From 9adbb3d49899a87b3026c11cb4ba3ff77f4fb75b Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Sat, 19 Aug 2023 02:31:57 +0200 Subject: chore: import VIATRA source Make our modifications more maintainable by editing the source code directly instead of using reflection. --- .editorconfig | 8 +- LICENSES/LicenseRef-EPL-Steward.txt | 1 + .../gradle/internal/java-conventions.gradle.kts | 3 - gradle/libs.versions.toml | 4 +- settings.gradle.kts | 7 + subprojects/language-semantics/build.gradle.kts | 4 +- subprojects/store-query-viatra/build.gradle.kts | 8 +- .../rete/network/RefineryConnectionFactory.java | 34 - .../runtime/rete/network/RefineryNodeFactory.java | 37 - .../query/viatra/ViatraModelQueryBuilder.java | 6 +- .../query/viatra/ViatraModelQueryStoreAdapter.java | 2 +- .../query/viatra/internal/RelationalScope.java | 8 +- .../internal/ViatraModelQueryAdapterImpl.java | 81 +- .../internal/ViatraModelQueryBuilderImpl.java | 16 +- .../internal/ViatraModelQueryStoreAdapterImpl.java | 6 +- .../viatra/internal/context/DummyBaseIndexer.java | 10 +- .../internal/context/RelationalEngineContext.java | 6 +- .../context/RelationalQueryMetaContext.java | 8 +- .../internal/context/RelationalRuntimeContext.java | 12 +- .../localsearch/ExtendOperationExecutor.java | 8 +- .../localsearch/ExtendPositivePatternCall.java | 18 +- .../internal/localsearch/FlatCostFunction.java | 12 +- .../internal/localsearch/GenericTypeExtend.java | 18 +- .../RelationalLocalSearchBackendFactory.java | 22 +- .../RelationalLocalSearchResultProvider.java | 16 +- .../localsearch/RelationalOperationCompiler.java | 24 +- .../internal/matcher/AbstractViatraMatcher.java | 4 +- .../viatra/internal/matcher/FunctionalCursor.java | 4 +- .../internal/matcher/FunctionalViatraMatcher.java | 16 +- .../viatra/internal/matcher/IndexerUtils.java | 53 - .../viatra/internal/matcher/MatcherUtils.java | 6 +- .../viatra/internal/matcher/RawPatternMatcher.java | 6 +- .../viatra/internal/matcher/RelationalCursor.java | 2 +- .../internal/matcher/RelationalViatraMatcher.java | 16 +- .../internal/matcher/UnsafeFunctionalCursor.java | 2 +- .../viatra/internal/pquery/CheckEvaluator.java | 2 +- .../query/viatra/internal/pquery/Dnf2PQuery.java | 68 +- .../internal/pquery/QueryWrapperFactory.java | 22 +- .../query/viatra/internal/pquery/RawPQuery.java | 16 +- .../pquery/RepresentativeElectionConstraint.java | 45 - .../pquery/StatefulMultisetAggregator.java | 2 +- .../pquery/StatelessMultisetAggregator.java | 2 +- .../viatra/internal/pquery/SymbolViewWrapper.java | 2 +- .../viatra/internal/pquery/TermEvaluator.java | 4 +- .../pquery/ValueProviderBasedValuation.java | 2 +- .../pquery/rewriter/RefineryPBodyCopier.java | 35 - .../pquery/rewriter/RefineryPBodyNormalizer.java | 38 - .../rewriter/RefinerySurrogateQueryRewriter.java | 58 - .../internal/rete/RefineryReteBackendFactory.java | 90 -- .../viatra/internal/rete/RefineryReteEngine.java | 134 -- .../internal/rete/RefineryReteRecipeCompiler.java | 228 --- .../RefineryConnectionFactoryExtensions.java | 47 - .../network/RefineryNodeFactoryExtensions.java | 44 - .../network/RepresentativeElectionAlgorithm.java | 137 -- .../rete/network/RepresentativeElectionNode.java | 120 -- .../StronglyConnectedComponentAlgorithm.java | 66 - .../network/WeaklyConnectedComponentAlgorithm.java | 85 - .../rete/recipe/RefineryRecipesFactory.java | 22 - .../rete/recipe/RefineryRecipesFactoryImpl.java | 74 - .../rete/recipe/RefineryRecipesPackage.java | 41 - .../rete/recipe/RefineryRecipesPackageImpl.java | 96 -- .../rete/recipe/RepresentativeElectionRecipe.java | 17 - .../recipe/RepresentativeElectionRecipeImpl.java | 105 -- .../internal/update/ModelUpdateListener.java | 6 +- .../viatra/internal/update/RelationViewFilter.java | 8 +- .../internal/update/SymbolViewUpdateListener.java | 8 +- .../update/TupleChangingViewUpdateListener.java | 2 +- .../update/TuplePreservingViewUpdateListener.java | 2 +- .../store/query/viatra/DiagonalQueryTest.java | 2 +- .../store/query/viatra/FunctionalQueryTest.java | 2 +- .../refinery/store/query/viatra/QueryTest.java | 2 +- .../store/query/viatra/QueryTransactionTest.java | 2 +- .../viatra/internal/matcher/MatcherUtilsTest.java | 2 +- .../store/query/viatra/tests/QueryBackendHint.java | 2 +- .../viatra/tests/QueryEvaluationHintSource.java | 2 +- subprojects/viatra-runtime-base-itc/about.html | 26 + .../viatra-runtime-base-itc/build.gradle.kts | 13 + .../runtime/base/itc/alg/counting/CountingAlg.java | 226 +++ .../base/itc/alg/counting/CountingTcRelation.java | 259 +++ .../viatra/runtime/base/itc/alg/dred/DRedAlg.java | 308 ++++ .../runtime/base/itc/alg/dred/DRedTcRelation.java | 223 +++ .../runtime/base/itc/alg/fw/FloydWarshallAlg.java | 110 ++ .../base/itc/alg/incscc/CountingListener.java | 36 + .../runtime/base/itc/alg/incscc/IncSCCAlg.java | 645 ++++++++ .../runtime/base/itc/alg/misc/DFSPathFinder.java | 146 ++ .../viatra/runtime/base/itc/alg/misc/Edge.java | 38 + .../runtime/base/itc/alg/misc/GraphHelper.java | 173 ++ .../base/itc/alg/misc/IGraphPathFinder.java | 67 + .../runtime/base/itc/alg/misc/ITcRelation.java | 31 + .../viatra/runtime/base/itc/alg/misc/Tuple.java | 60 + .../viatra/runtime/base/itc/alg/misc/bfs/BFS.java | 151 ++ .../runtime/base/itc/alg/misc/dfs/DFSAlg.java | 93 ++ .../runtime/base/itc/alg/misc/scc/PKAlg.java | 179 ++ .../viatra/runtime/base/itc/alg/misc/scc/SCC.java | 146 ++ .../runtime/base/itc/alg/misc/scc/SCCProperty.java | 37 + .../runtime/base/itc/alg/misc/scc/SCCResult.java | 81 + .../itc/alg/misc/topsort/TopologicalSorting.java | 77 + .../RepresentativeElectionAlgorithm.java | 140 ++ .../alg/representative/RepresentativeObserver.java | 12 + .../StronglyConnectedComponentAlgorithm.java | 66 + .../WeaklyConnectedComponentAlgorithm.java | 85 + .../base/itc/alg/util/CollectionHelper.java | 64 + .../runtime/base/itc/graphimpl/DotGenerator.java | 160 ++ .../viatra/runtime/base/itc/graphimpl/Graph.java | 185 +++ .../itc/igraph/IBiDirectionalGraphDataSource.java | 37 + .../base/itc/igraph/IBiDirectionalWrapper.java | 110 ++ .../runtime/base/itc/igraph/IGraphDataSource.java | 70 + .../runtime/base/itc/igraph/IGraphObserver.java | 55 + .../runtime/base/itc/igraph/ITcDataSource.java | 82 + .../runtime/base/itc/igraph/ITcObserver.java | 39 + subprojects/viatra-runtime-base/about.html | 26 + subprojects/viatra-runtime-base/build.gradle.kts | 19 + .../viatra/runtime/base/ViatraBasePlugin.java | 46 + .../viatra/runtime/base/api/BaseIndexOptions.java | 395 +++++ .../viatra/runtime/base/api/DataTypeListener.java | 44 + .../base/api/EMFBaseIndexChangeListener.java | 33 + .../viatra/runtime/base/api/FeatureListener.java | 48 + .../runtime/base/api/IEClassifierProcessor.java | 25 + .../base/api/IEMFIndexingErrorListener.java | 22 + .../runtime/base/api/IQueryResultSetter.java | 63 + .../base/api/IQueryResultUpdateListener.java | 45 + .../api/IStructuralFeatureInstanceProcessor.java | 19 + .../viatra/runtime/base/api/IndexingLevel.java | 113 ++ .../viatra/runtime/base/api/InstanceListener.java | 41 + .../base/api/LightweightEObjectObserver.java | 32 + .../api/LightweightEObjectObserverAdapter.java | 74 + .../viatra/runtime/base/api/NavigationHelper.java | 885 ++++++++++ .../base/api/QueryResultAssociativeStore.java | 322 ++++ .../viatra/runtime/base/api/QueryResultMap.java | 210 +++ .../runtime/base/api/TransitiveClosureHelper.java | 26 + .../viatra/runtime/base/api/ViatraBaseFactory.java | 180 +++ .../base/api/filters/IBaseIndexFeatureFilter.java | 38 + .../base/api/filters/IBaseIndexObjectFilter.java | 30 + .../base/api/filters/IBaseIndexResourceFilter.java | 27 + .../base/api/filters/SimpleBaseIndexFilter.java | 46 + .../base/api/profiler/BaseIndexProfiler.java | 79 + .../runtime/base/api/profiler/ProfilerMode.java | 22 + .../base/comprehension/EMFModelComprehension.java | 356 ++++ .../runtime/base/comprehension/EMFVisitor.java | 145 ++ .../WellbehavingDerivedFeatureRegistry.java | 154 ++ .../runtime/base/core/AbstractBaseIndexStore.java | 28 + .../base/core/EMFBaseIndexInstanceStore.java | 451 ++++++ .../runtime/base/core/EMFBaseIndexMetaStore.java | 380 +++++ .../base/core/EMFBaseIndexStatisticsStore.java | 71 + .../viatra/runtime/base/core/EMFDataSource.java | 137 ++ .../base/core/NavigationHelperContentAdapter.java | 750 +++++++++ .../runtime/base/core/NavigationHelperImpl.java | 1702 ++++++++++++++++++++ .../runtime/base/core/NavigationHelperSetting.java | 73 + .../runtime/base/core/NavigationHelperType.java | 14 + .../runtime/base/core/NavigationHelperVisitor.java | 441 +++++ .../base/core/TransitiveClosureHelperImpl.java | 153 ++ .../ProfilingNavigationHelperContentAdapter.java | 155 ++ .../base/exception/ViatraBaseException.java | 25 + subprojects/viatra-runtime-localsearch/about.html | 26 + .../viatra-runtime-localsearch/build.gradle.kts | 15 + .../localsearch/ExecutionLoggerAdapter.java | 85 + .../viatra/runtime/localsearch/MatchingFrame.java | 122 ++ .../exceptions/LocalSearchException.java | 33 + .../localsearch/matcher/CallWithAdornment.java | 55 + .../localsearch/matcher/ILocalSearchAdaptable.java | 29 + .../localsearch/matcher/ILocalSearchAdapter.java | 120 ++ .../localsearch/matcher/ISearchContext.java | 143 ++ .../localsearch/matcher/LocalSearchMatcher.java | 301 ++++ .../localsearch/matcher/MatcherReference.java | 97 ++ .../AbstractLocalSearchResultProvider.java | 532 ++++++ .../matcher/integration/AllValidAdornments.java | 37 + .../integration/DontFlattenDisjunctive.java | 29 + .../DontFlattenIncrementalPredicate.java | 44 + .../GenericLocalSearchResultProvider.java | 49 + .../matcher/integration/IAdornmentProvider.java | 72 + .../integration/LazyPlanningAdornments.java | 41 + .../matcher/integration/LocalSearchBackend.java | 259 +++ .../LocalSearchBackendFactoryProvider.java | 29 + .../integration/LocalSearchEMFBackendFactory.java | 65 + .../LocalSearchGenericBackendFactory.java | 65 + .../LocalSearchGenericBackendFactoryProvider.java | 24 + .../integration/LocalSearchHintOptions.java | 70 + .../matcher/integration/LocalSearchHints.java | 357 ++++ .../integration/LocalSearchResultProvider.java | 56 + .../operations/CheckOperationExecutor.java | 50 + .../operations/ExtendOperationExecutor.java | 72 + .../operations/IIteratingSearchOperation.java | 27 + .../operations/IPatternMatcherOperation.java | 26 + .../localsearch/operations/ISearchOperation.java | 88 + .../operations/MatchingFrameValueProvider.java | 41 + .../operations/check/AggregatorCheck.java | 116 ++ .../check/BinaryTransitiveClosureCheck.java | 135 ++ .../operations/check/CheckConstant.java | 70 + .../operations/check/CheckPositivePatternCall.java | 96 ++ .../operations/check/ContainmentCheck.java | 85 + .../localsearch/operations/check/CountCheck.java | 92 ++ .../operations/check/ExpressionCheck.java | 78 + .../operations/check/ExpressionEvalCheck.java | 95 ++ .../operations/check/InequalityCheck.java | 77 + .../operations/check/InstanceOfClassCheck.java | 75 + .../operations/check/InstanceOfDataTypeCheck.java | 71 + .../operations/check/InstanceOfJavaClassCheck.java | 71 + .../localsearch/operations/check/NACOperation.java | 89 + .../operations/check/StructuralFeatureCheck.java | 91 ++ .../operations/check/nobase/ScopeCheck.java | 91 ++ .../operations/extend/AggregatorExtend.java | 106 ++ .../operations/extend/CountOperation.java | 88 + .../operations/extend/ExpressionEval.java | 104 ++ .../extend/ExtendBinaryTransitiveClosure.java | 185 +++ .../operations/extend/ExtendConstant.java | 75 + .../extend/ExtendPositivePatternCall.java | 118 ++ .../extend/ExtendToEStructuralFeatureSource.java | 112 ++ .../extend/ExtendToEStructuralFeatureTarget.java | 102 ++ .../operations/extend/IterateOverChildren.java | 90 ++ .../operations/extend/IterateOverContainers.java | 126 ++ .../extend/IterateOverEClassInstances.java | 97 ++ .../extend/IterateOverEDatatypeInstances.java | 96 ++ .../IterateOverEStructuralFeatureInstances.java | 115 ++ .../extend/SingleValueExtendOperationExecutor.java | 40 + .../AbstractIteratingExtendOperationExecutor.java | 54 + .../nobase/ExtendToEStructuralFeatureSource.java | 118 ++ .../extend/nobase/IterateOverEClassInstances.java | 93 ++ .../nobase/IterateOverEDatatypeInstances.java | 123 ++ .../operations/generic/GenericTypeCheck.java | 96 ++ .../operations/generic/GenericTypeExtend.java | 145 ++ .../generic/GenericTypeExtendSingleValue.java | 114 ++ .../operations/util/CallInformation.java | 186 +++ .../runtime/localsearch/plan/IPlanDescriptor.java | 49 + .../runtime/localsearch/plan/IPlanProvider.java | 33 + .../runtime/localsearch/plan/PlanDescriptor.java | 84 + .../runtime/localsearch/plan/SearchPlan.java | 99 ++ .../localsearch/plan/SearchPlanExecutor.java | 210 +++ .../localsearch/plan/SearchPlanForBody.java | 115 ++ .../localsearch/plan/SimplePlanProvider.java | 49 + .../localsearch/planner/ILocalSearchPlanner.java | 38 + .../planner/ISearchPlanCodeGenerator.java | 23 + .../localsearch/planner/LocalSearchPlanner.java | 140 ++ .../planner/LocalSearchRuntimeBasedStrategy.java | 257 +++ .../localsearch/planner/PConstraintCategory.java | 41 + .../localsearch/planner/PConstraintInfo.java | 142 ++ .../planner/PConstraintInfoInferrer.java | 326 ++++ .../runtime/localsearch/planner/PlanState.java | 283 ++++ .../compiler/AbstractOperationCompiler.java | 430 +++++ .../planner/compiler/EMFOperationCompiler.java | 198 +++ .../planner/compiler/GenericOperationCompiler.java | 102 ++ .../planner/compiler/IOperationCompiler.java | 53 + .../planner/cost/IConstraintEvaluationContext.java | 63 + .../localsearch/planner/cost/ICostFunction.java | 22 + .../impl/HybridMatcherConstraintCostFunction.java | 91 ++ .../impl/IndexerBasedConstraintCostFunction.java | 49 + .../StatisticsBasedConstraintCostFunction.java | 413 +++++ .../impl/VariableBindingBasedCostFunction.java | 95 ++ .../localsearch/planner/util/CompilerHelper.java | 209 +++ .../planner/util/OperationCostComparator.java | 26 + .../profiler/LocalSearchProfilerAdapter.java | 83 + subprojects/viatra-runtime-matchers/about.html | 26 + .../viatra-runtime-matchers/build.gradle.kts | 15 + .../matchers/ViatraQueryRuntimeException.java | 42 + .../matchers/aggregators/AverageAccumulator.java | 24 + .../aggregators/DoubleAverageOperator.java | 82 + .../matchers/aggregators/DoubleSumOperator.java | 62 + .../matchers/aggregators/ExtremumOperator.java | 135 ++ .../aggregators/IntegerAverageOperator.java | 82 + .../matchers/aggregators/IntegerSumOperator.java | 61 + .../matchers/aggregators/LongAverageOperator.java | 82 + .../matchers/aggregators/LongSumOperator.java | 61 + .../viatra/runtime/matchers/aggregators/avg.java | 39 + .../viatra/runtime/matchers/aggregators/count.java | 33 + .../viatra/runtime/matchers/aggregators/max.java | 44 + .../viatra/runtime/matchers/aggregators/min.java | 44 + .../viatra/runtime/matchers/aggregators/sum.java | 39 + .../matchers/algorithms/OrderedIterableMerge.java | 83 + .../runtime/matchers/algorithms/UnionFind.java | 214 +++ .../matchers/algorithms/UnionFindNodeProperty.java | 32 + .../matchers/backend/CommonQueryHintOptions.java | 36 + .../matchers/backend/ICallDelegationStrategy.java | 89 + .../matchers/backend/IMatcherCapability.java | 26 + .../runtime/matchers/backend/IQueryBackend.java | 68 + .../matchers/backend/IQueryBackendFactory.java | 52 + .../backend/IQueryBackendFactoryProvider.java | 46 + .../backend/IQueryBackendHintProvider.java | 32 + .../matchers/backend/IQueryResultProvider.java | 202 +++ .../runtime/matchers/backend/IUpdateable.java | 27 + .../matchers/backend/QueryEvaluationHint.java | 241 +++ .../runtime/matchers/backend/QueryHintOption.java | 122 ++ .../matchers/backend/ResultProviderRequestor.java | 74 + .../matchers/context/AbstractQueryMetaContext.java | 69 + .../context/AbstractQueryRuntimeContext.java | 21 + .../viatra/runtime/matchers/context/IInputKey.java | 45 + .../runtime/matchers/context/IPosetComparator.java | 35 + .../matchers/context/IQueryBackendContext.java | 49 + .../matchers/context/IQueryCacheContext.java | 39 + .../matchers/context/IQueryMetaContext.java | 98 ++ .../context/IQueryResultProviderAccess.java | 31 + .../matchers/context/IQueryRuntimeContext.java | 281 ++++ .../context/IQueryRuntimeContextListener.java | 27 + .../runtime/matchers/context/IndexingService.java | 36 + .../matchers/context/InputKeyImplication.java | 103 ++ .../context/common/BaseInputKeyWrapper.java | 55 + .../context/common/JavaTransitiveInstancesKey.java | 165 ++ .../context/surrogate/SurrogateQueryRegistry.java | 153 ++ .../memories/AbstractTrivialMaskedMemory.java | 58 + .../memories/DefaultMaskedTupleMemory.java | 127 ++ .../memories/IdentityMaskedTupleMemory.java | 77 + .../matchers/memories/MaskedTupleMemory.java | 385 +++++ .../memories/NullaryMaskedTupleMemory.java | 85 + .../matchers/memories/UnaryMaskedTupleMemory.java | 143 ++ .../timely/AbstractTimelyMaskedMemory.java | 228 +++ .../timely/AbstractTimelyTrivialMaskedMemory.java | 100 ++ .../timely/TimelyDefaultMaskedTupleMemory.java | 98 ++ .../timely/TimelyIdentityMaskedTupleMemory.java | 106 ++ .../timely/TimelyNullaryMaskedTupleMemory.java | 108 ++ .../timely/TimelyUnaryMaskedTupleMemory.java | 133 ++ .../matchers/planning/IOperationCompiler.java | 108 ++ .../matchers/planning/IQueryPlannerStrategy.java | 29 + .../planning/QueryProcessingException.java | 102 ++ .../viatra/runtime/matchers/planning/SubPlan.java | 240 +++ .../runtime/matchers/planning/SubPlanFactory.java | 33 + .../matchers/planning/helpers/BuildHelper.java | 165 ++ .../helpers/FunctionalDependencyHelper.java | 143 ++ .../planning/helpers/StatisticsHelper.java | 62 + .../matchers/planning/helpers/TypeHelper.java | 217 +++ .../matchers/planning/operations/PApply.java | 94 ++ .../matchers/planning/operations/PEnumerate.java | 76 + .../matchers/planning/operations/PJoin.java | 64 + .../matchers/planning/operations/POperation.java | 52 + .../matchers/planning/operations/PProject.java | 109 ++ .../matchers/planning/operations/PStart.java | 90 ++ .../runtime/matchers/psystem/BasePConstraint.java | 108 ++ .../matchers/psystem/DeferredPConstraint.java | 31 + .../matchers/psystem/EnumerablePConstraint.java | 59 + .../matchers/psystem/IExpressionEvaluator.java | 42 + .../matchers/psystem/IMultiQueryReference.java | 26 + .../runtime/matchers/psystem/IQueryReference.java | 33 + .../matchers/psystem/IRelationEvaluator.java | 47 + .../runtime/matchers/psystem/ITypeConstraint.java | 65 + .../psystem/ITypeInfoProviderConstraint.java | 28 + .../runtime/matchers/psystem/IValueProvider.java | 27 + .../matchers/psystem/InitializablePQuery.java | 56 + .../psystem/KeyedEnumerablePConstraint.java | 39 + .../viatra/runtime/matchers/psystem/PBody.java | 289 ++++ .../runtime/matchers/psystem/PConstraint.java | 70 + .../runtime/matchers/psystem/PTraceable.java | 16 + .../viatra/runtime/matchers/psystem/PVariable.java | 203 +++ .../runtime/matchers/psystem/TypeJudgement.java | 153 ++ .../psystem/VariableDeferredPConstraint.java | 40 + .../AbstractMemorylessAggregationOperator.java | 31 + .../psystem/aggregations/AggregatorType.java | 49 + .../psystem/aggregations/BoundAggregator.java | 61 + .../psystem/aggregations/IAggregatorFactory.java | 40 + .../aggregations/IMultisetAggregationOperator.java | 106 ++ .../matchers/psystem/analysis/QueryAnalyzer.java | 194 +++ .../matchers/psystem/annotations/PAnnotation.java | 94 ++ .../psystem/annotations/ParameterReference.java | 30 + .../basicdeferred/AggregatorConstraint.java | 98 ++ .../basicdeferred/BaseTypeSafeConstraint.java | 99 ++ .../matchers/psystem/basicdeferred/Equality.java | 96 ++ .../psystem/basicdeferred/ExportedParameter.java | 108 ++ .../basicdeferred/ExpressionEvaluation.java | 80 + .../matchers/psystem/basicdeferred/Inequality.java | 151 ++ .../psystem/basicdeferred/NegativePatternCall.java | 52 + .../basicdeferred/PatternCallBasedDeferred.java | 118 ++ .../psystem/basicdeferred/PatternMatchCounter.java | 70 + .../psystem/basicdeferred/RelationEvaluation.java | 57 + .../basicdeferred/TypeFilterConstraint.java | 105 ++ .../AbstractTransitiveClosure.java | 44 + .../BinaryReflexiveTransitiveClosure.java | 57 + .../basicenumerables/BinaryTransitiveClosure.java | 33 + .../psystem/basicenumerables/Connectivity.java | 11 + .../psystem/basicenumerables/ConstantValue.java | 57 + .../basicenumerables/PositivePatternCall.java | 76 + .../RepresentativeElectionConstraint.java | 43 + .../psystem/basicenumerables/TypeConstraint.java | 79 + .../matchers/psystem/queries/BasePQuery.java | 231 +++ .../matchers/psystem/queries/PDisjunction.java | 104 ++ .../matchers/psystem/queries/PParameter.java | 105 ++ .../psystem/queries/PParameterDirection.java | 35 + .../runtime/matchers/psystem/queries/PProblem.java | 68 + .../runtime/matchers/psystem/queries/PQueries.java | 110 ++ .../runtime/matchers/psystem/queries/PQuery.java | 154 ++ .../matchers/psystem/queries/PQueryHeader.java | 101 ++ .../matchers/psystem/queries/PVisibility.java | 37 + .../queries/QueryInitializationException.java | 35 + .../rewriters/AbstractRewriterTraceSource.java | 53 + .../psystem/rewriters/ConstraintRemovalReason.java | 23 + .../rewriters/DefaultFlattenCallPredicate.java | 23 + .../psystem/rewriters/FlattenerCopier.java | 129 ++ .../psystem/rewriters/IConstraintFilter.java | 48 + .../rewriters/IDerivativeModificationReason.java | 19 + .../psystem/rewriters/IFlattenCallPredicate.java | 50 + .../rewriters/IPTraceableTraceProvider.java | 55 + .../psystem/rewriters/IRewriterTraceCollector.java | 33 + .../psystem/rewriters/IVariableRenamer.java | 59 + .../psystem/rewriters/MappingTraceCollector.java | 135 ++ .../rewriters/NeverFlattenCallPredicate.java | 26 + .../psystem/rewriters/NopTraceCollector.java | 68 + .../matchers/psystem/rewriters/PBodyCopier.java | 306 ++++ .../psystem/rewriters/PBodyNormalizer.java | 310 ++++ .../psystem/rewriters/PDisjunctionRewriter.java | 27 + .../rewriters/PDisjunctionRewriterCacher.java | 64 + .../psystem/rewriters/PQueryFlattener.java | 253 +++ .../psystem/rewriters/RewriterException.java | 31 + .../psystem/rewriters/SurrogateQueryRewriter.java | 63 + .../VariableMappingExpressionEvaluatorWrapper.java | 88 + .../runtime/matchers/scopes/IStorageBackend.java | 53 + .../matchers/scopes/SimpleLocalStorageBackend.java | 51 + .../matchers/scopes/SimpleRuntimeContext.java | 132 ++ .../matchers/scopes/TabularRuntimeContext.java | 119 ++ .../matchers/scopes/tables/AbstractIndexTable.java | 266 +++ .../matchers/scopes/tables/DefaultIndexTable.java | 143 ++ .../matchers/scopes/tables/DisjointUnionTable.java | 192 +++ .../matchers/scopes/tables/IIndexTable.java | 212 +++ .../matchers/scopes/tables/ITableContext.java | 29 + .../matchers/scopes/tables/ITableWriterBinary.java | 53 + .../scopes/tables/ITableWriterGeneric.java | 52 + .../matchers/scopes/tables/ITableWriterUnary.java | 52 + .../matchers/scopes/tables/SimpleBinaryTable.java | 320 ++++ .../matchers/scopes/tables/SimpleUnaryTable.java | 140 ++ .../runtime/matchers/tuple/AbstractTuple.java | 136 ++ .../runtime/matchers/tuple/BaseFlatTuple.java | 20 + .../matchers/tuple/BaseLeftInheritanceTuple.java | 65 + .../viatra/runtime/matchers/tuple/FlatTuple.java | 60 + .../viatra/runtime/matchers/tuple/FlatTuple0.java | 46 + .../viatra/runtime/matchers/tuple/FlatTuple1.java | 44 + .../viatra/runtime/matchers/tuple/FlatTuple2.java | 51 + .../viatra/runtime/matchers/tuple/FlatTuple3.java | 55 + .../viatra/runtime/matchers/tuple/FlatTuple4.java | 59 + .../runtime/matchers/tuple/IModifiableTuple.java | 27 + .../viatra/runtime/matchers/tuple/ITuple.java | 64 + .../matchers/tuple/LeftInheritanceTuple.java | 172 ++ .../matchers/tuple/LeftInheritanceTuple1.java | 83 + .../matchers/tuple/LeftInheritanceTuple2.java | 73 + .../matchers/tuple/LeftInheritanceTuple3.java | 81 + .../matchers/tuple/LeftInheritanceTuple4.java | 88 + .../viatra/runtime/matchers/tuple/MaskedTuple.java | 48 + .../viatra/runtime/matchers/tuple/Tuple.java | 69 + .../viatra/runtime/matchers/tuple/TupleMask.java | 560 +++++++ .../viatra/runtime/matchers/tuple/TupleMask0.java | 56 + .../runtime/matchers/tuple/TupleMaskIdentity.java | 51 + .../runtime/matchers/tuple/TupleValueProvider.java | 48 + .../viatra/runtime/matchers/tuple/Tuples.java | 157 ++ .../matchers/tuple/VolatileMaskedTuple.java | 50 + .../tuple/VolatileModifiableMaskedTuple.java | 47 + .../runtime/matchers/tuple/VolatileTuple.java | 47 + .../viatra/runtime/matchers/util/Accuracy.java | 48 + .../viatra/runtime/matchers/util/Clearable.java | 23 + .../runtime/matchers/util/CollectionsFactory.java | 188 +++ .../viatra/runtime/matchers/util/Direction.java | 61 + .../matchers/util/EclipseCollectionsBagMemory.java | 86 + .../matchers/util/EclipseCollectionsDeltaBag.java | 41 + .../matchers/util/EclipseCollectionsFactory.java | 159 ++ .../util/EclipseCollectionsLongMultiset.java | 150 ++ .../util/EclipseCollectionsLongSetMemory.java | 212 +++ .../util/EclipseCollectionsMultiLookup.java | 226 +++ .../matchers/util/EclipseCollectionsMultiset.java | 93 ++ .../matchers/util/EclipseCollectionsSetMemory.java | 94 ++ .../viatra/runtime/matchers/util/EmptyMemory.java | 93 ++ .../viatra/runtime/matchers/util/ICache.java | 32 + .../viatra/runtime/matchers/util/IDeltaBag.java | 26 + .../viatra/runtime/matchers/util/IMemory.java | 81 + .../viatra/runtime/matchers/util/IMemoryView.java | 205 +++ .../viatra/runtime/matchers/util/IMultiLookup.java | 216 +++ .../matchers/util/IMultiLookupAbstract.java | 485 ++++++ .../viatra/runtime/matchers/util/IMultiset.java | 30 + .../viatra/runtime/matchers/util/IProvider.java | 30 + .../viatra/runtime/matchers/util/ISetMemory.java | 37 + .../runtime/matchers/util/MapBackedMemoryView.java | 102 ++ .../viatra/runtime/matchers/util/MarkedMemory.java | 21 + .../matchers/util/MemoryViewBackedMapView.java | 117 ++ .../runtime/matchers/util/Preconditions.java | 208 +++ .../runtime/matchers/util/PurgableCache.java | 44 + .../viatra/runtime/matchers/util/Sets.java | 90 ++ .../viatra/runtime/matchers/util/Signed.java | 60 + .../matchers/util/SingletonInstanceProvider.java | 29 + .../runtime/matchers/util/SingletonMemoryView.java | 105 ++ .../viatra/runtime/matchers/util/TimelyMemory.java | 517 ++++++ .../matchers/util/resumable/MaskedResumable.java | 36 + .../runtime/matchers/util/resumable/Resumable.java | 27 + .../matchers/util/resumable/UnmaskedResumable.java | 36 + .../matchers/util/timeline/CompactTimeline.java | 111 ++ .../runtime/matchers/util/timeline/Diff.java | 55 + .../matchers/util/timeline/SingletonTimeline.java | 73 + .../runtime/matchers/util/timeline/Timeline.java | 146 ++ .../runtime/matchers/util/timeline/Timelines.java | 46 + .../META-INF/MANIFEST.MF | 18 + .../META-INF/MANIFEST.MF.license | 4 + subprojects/viatra-runtime-rete-recipes/about.html | 26 + .../viatra-runtime-rete-recipes/build.gradle.kts | 62 + .../viatra-runtime-rete-recipes/build.properties | 15 + .../viatra-runtime-rete-recipes/plugin.properties | 9 + subprojects/viatra-runtime-rete-recipes/plugin.xml | 23 + .../runtime/rete/recipes/GenerateReteRecipes.mwe2 | 25 + .../rete/recipes/helper/RecipeRecognizer.java | 204 +++ .../runtime/rete/recipes/helper/RecipesHelper.java | 81 + .../src/main/resources/model/recipes.ecore | 400 +++++ .../src/main/resources/model/recipes.ecore.license | 4 + .../src/main/resources/model/rete-recipes.genmodel | 171 ++ .../resources/model/rete-recipes.genmodel.license | 4 + subprojects/viatra-runtime-rete/about.html | 26 + subprojects/viatra-runtime-rete/build.gradle.kts | 15 + .../aggregation/AbstractColumnAggregatorNode.java | 474 ++++++ .../rete/aggregation/ColumnAggregatorNode.java | 369 +++++ .../viatra/runtime/rete/aggregation/CountNode.java | 38 + .../runtime/rete/aggregation/GroupedMap.java | 120 ++ .../runtime/rete/aggregation/GroupedSet.java | 114 ++ .../runtime/rete/aggregation/IAggregatorNode.java | 26 + .../aggregation/IndexerBasedAggregatorNode.java | 278 ++++ ...FaithfulParallelTimelyColumnAggregatorNode.java | 217 +++ ...ithfulSequentialTimelyColumnAggregatorNode.java | 280 ++++ .../timely/FaithfulTimelyColumnAggregatorNode.java | 247 +++ ...irstOnlyParallelTimelyColumnAggregatorNode.java | 106 ++ ...stOnlySequentialTimelyColumnAggregatorNode.java | 117 ++ .../FirstOnlyTimelyColumnAggregatorNode.java | 212 +++ .../runtime/rete/boundary/Disconnectable.java | 26 + .../rete/boundary/ExternalInputEnumeratorNode.java | 209 +++ .../boundary/ExternalInputStatelessFilterNode.java | 68 + .../runtime/rete/boundary/InputConnector.java | 208 +++ .../viatra/runtime/rete/boundary/ReteBoundary.java | 551 +++++++ .../construction/RetePatternBuildException.java | 50 + .../basiclinear/BasicLinearLayout.java | 171 ++ .../basiclinear/OrderingHeuristics.java | 90 ++ .../construction/plancompiler/CompilerHelper.java | 390 +++++ .../plancompiler/RecursionCutoffPoint.java | 86 + .../plancompiler/ReteRecipeCompiler.java | 948 +++++++++++ .../rete/construction/quasitree/JoinCandidate.java | 162 ++ .../quasitree/JoinOrderingHeuristics.java | 49 + .../construction/quasitree/QuasiTreeLayout.java | 205 +++ .../rete/construction/quasitree/TieBreaker.java | 30 + .../runtime/rete/eval/AbstractEvaluatorNode.java | 65 + .../viatra/runtime/rete/eval/EvaluatorCore.java | 180 +++ .../viatra/runtime/rete/eval/IEvaluatorNode.java | 25 + .../runtime/rete/eval/MemorylessEvaluatorNode.java | 75 + .../rete/eval/OutputCachingEvaluatorNode.java | 311 ++++ .../runtime/rete/eval/RelationEvaluatorNode.java | 183 +++ .../runtime/rete/index/DefaultIndexerListener.java | 28 + .../viatra/runtime/rete/index/DualInputNode.java | 348 ++++ .../viatra/runtime/rete/index/ExistenceNode.java | 199 +++ .../rete/index/GenericProjectionIndexer.java | 76 + .../viatra/runtime/rete/index/IdentityIndexer.java | 76 + .../viatra/runtime/rete/index/Indexer.java | 71 + .../viatra/runtime/rete/index/IndexerListener.java | 41 + .../runtime/rete/index/IndexerWithMemory.java | 284 ++++ .../viatra/runtime/rete/index/IterableIndexer.java | 34 + .../viatra/runtime/rete/index/JoinNode.java | 193 +++ .../runtime/rete/index/MemoryIdentityIndexer.java | 55 + .../runtime/rete/index/MemoryNullIndexer.java | 54 + .../viatra/runtime/rete/index/NullIndexer.java | 88 + .../viatra/runtime/rete/index/OnetimeIndexer.java | 47 + .../runtime/rete/index/ProjectionIndexer.java | 21 + .../rete/index/SpecializedProjectionIndexer.java | 176 ++ .../viatra/runtime/rete/index/StandardIndexer.java | 127 ++ .../rete/index/TransitiveClosureNodeIndexer.java | 121 ++ .../index/timely/TimelyMemoryIdentityIndexer.java | 51 + .../rete/index/timely/TimelyMemoryNullIndexer.java | 49 + .../rete/matcher/DRedReteBackendFactory.java | 49 + .../runtime/rete/matcher/HintConfigurator.java | 46 + .../rete/matcher/IncrementalMatcherCapability.java | 30 + .../runtime/rete/matcher/ReteBackendFactory.java | 100 ++ .../rete/matcher/ReteBackendFactoryProvider.java | 35 + .../viatra/runtime/rete/matcher/ReteEngine.java | 579 +++++++ .../runtime/rete/matcher/RetePatternMatcher.java | 462 ++++++ .../runtime/rete/matcher/TimelyConfiguration.java | 61 + .../rete/matcher/TimelyReteBackendFactory.java | 64 + .../refinery/viatra/runtime/rete/misc/Bag.java | 43 + .../viatra/runtime/rete/misc/ConstantNode.java | 50 + .../runtime/rete/misc/DefaultDeltaMonitor.java | 43 + .../viatra/runtime/rete/misc/DeltaMonitor.java | 111 ++ .../viatra/runtime/rete/misc/SimpleReceiver.java | 109 ++ .../viatra/runtime/rete/network/BaseNode.java | 108 ++ .../runtime/rete/network/ConnectionFactory.java | 170 ++ .../viatra/runtime/rete/network/IGroupable.java | 31 + .../viatra/runtime/rete/network/Network.java | 408 +++++ .../NetworkStructureChangeSensitiveNode.java | 30 + .../refinery/viatra/runtime/rete/network/Node.java | 62 + .../viatra/runtime/rete/network/NodeFactory.java | 375 +++++ .../runtime/rete/network/NodeProvisioner.java | 346 ++++ .../runtime/rete/network/PosetAwareReceiver.java | 39 + .../runtime/rete/network/ProductionNode.java | 28 + .../viatra/runtime/rete/network/Receiver.java | 85 + .../runtime/rete/network/RederivableNode.java | 34 + .../runtime/rete/network/ReinitializedNode.java | 14 + .../viatra/runtime/rete/network/ReteContainer.java | 729 +++++++++ .../viatra/runtime/rete/network/StandardNode.java | 121 ++ .../viatra/runtime/rete/network/Supplier.java | 82 + .../viatra/runtime/rete/network/Tunnel.java | 19 + .../viatra/runtime/rete/network/UpdateMessage.java | 31 + .../network/communication/CommunicationGroup.java | 103 ++ .../communication/CommunicationTracker.java | 467 ++++++ .../network/communication/MessageSelector.java | 19 + .../rete/network/communication/NodeComparator.java | 32 + .../rete/network/communication/PhasedSelector.java | 34 + .../rete/network/communication/Timestamp.java | 124 ++ .../timeless/RecursiveCommunicationGroup.java | 164 ++ .../timeless/SingletonCommunicationGroup.java | 86 + .../timeless/TimelessCommunicationTracker.java | 149 ++ .../communication/timely/ResumableNode.java | 36 + .../timely/TimelyCommunicationGroup.java | 171 ++ .../timely/TimelyCommunicationTracker.java | 216 +++ .../timely/TimelyIndexerListenerProxy.java | 81 + .../communication/timely/TimelyMailboxProxy.java | 102 ++ .../timely/TimestampTransformation.java | 48 + .../rete/network/delayed/DelayedCommand.java | 81 + .../network/delayed/DelayedConnectCommand.java | 27 + .../network/delayed/DelayedDisconnectCommand.java | 30 + .../network/indexer/DefaultMessageIndexer.java | 74 + .../network/indexer/GroupBasedMessageIndexer.java | 95 ++ .../rete/network/indexer/MessageIndexer.java | 33 + .../rete/network/mailbox/AdaptableMailbox.java | 32 + .../network/mailbox/FallThroughCapableMailbox.java | 30 + .../runtime/rete/network/mailbox/Mailbox.java | 78 + .../network/mailbox/MessageIndexerFactory.java | 23 + .../timeless/AbstractUpdateSplittingMailbox.java | 109 ++ .../mailbox/timeless/BehaviorChangingMailbox.java | 117 ++ .../network/mailbox/timeless/DefaultMailbox.java | 164 ++ .../mailbox/timeless/PosetAwareMailbox.java | 218 +++ .../mailbox/timeless/UpdateSplittingMailbox.java | 135 ++ .../rete/network/mailbox/timely/TimelyMailbox.java | 150 ++ .../viatra/runtime/rete/remote/Address.java | 125 ++ .../viatra/runtime/rete/remote/RemoteReceiver.java | 63 + .../viatra/runtime/rete/remote/RemoteSupplier.java | 54 + .../single/AbstractUniquenessEnforcerNode.java | 138 ++ .../viatra/runtime/rete/single/CallbackNode.java | 37 + .../runtime/rete/single/DefaultProductionNode.java | 79 + .../rete/single/DiscriminatorBucketNode.java | 85 + .../rete/single/DiscriminatorDispatcherNode.java | 154 ++ .../runtime/rete/single/EqualityFilterNode.java | 41 + .../viatra/runtime/rete/single/FilterNode.java | 69 + .../runtime/rete/single/InequalityFilterNode.java | 52 + .../rete/single/RepresentativeElectionNode.java | 125 ++ .../runtime/rete/single/SingleInputNode.java | 126 ++ .../runtime/rete/single/TimelyProductionNode.java | 63 + .../rete/single/TimelyUniquenessEnforcerNode.java | 161 ++ .../runtime/rete/single/TransformerNode.java | 49 + .../runtime/rete/single/TransitiveClosureNode.java | 147 ++ .../runtime/rete/single/TransparentNode.java | 48 + .../viatra/runtime/rete/single/TrimmerNode.java | 61 + .../rete/single/UniquenessEnforcerNode.java | 321 ++++ .../runtime/rete/single/ValueBinderFilterNode.java | 44 + .../rete/traceability/ActiveNodeConflictTrace.java | 24 + .../runtime/rete/traceability/CompiledQuery.java | 54 + .../runtime/rete/traceability/CompiledSubPlan.java | 51 + .../traceability/ParameterProjectionTrace.java | 42 + .../rete/traceability/PatternTraceInfo.java | 17 + .../runtime/rete/traceability/PlanningTrace.java | 80 + .../runtime/rete/traceability/RecipeTraceInfo.java | 81 + .../runtime/rete/traceability/TraceInfo.java | 33 + .../rete/traceability/UserRequestTrace.java | 36 + .../runtime/rete/util/LexicographicComparator.java | 58 + .../refinery/viatra/runtime/rete/util/Options.java | 111 ++ .../runtime/rete/util/OrderingCompareAgent.java | 92 ++ .../viatra/runtime/rete/util/ReteHintOptions.java | 60 + subprojects/viatra-runtime/about.html | 26 + subprojects/viatra-runtime/build.gradle.kts | 17 + .../tools/refinery/viatra/runtime/IExtensions.java | 24 + .../viatra/runtime/ViatraQueryRuntimePlugin.java | 32 + .../runtime/api/AdvancedViatraQueryEngine.java | 368 +++++ .../viatra/runtime/api/GenericPatternMatch.java | 166 ++ .../viatra/runtime/api/GenericPatternMatcher.java | 83 + .../viatra/runtime/api/GenericQueryGroup.java | 82 + .../runtime/api/GenericQuerySpecification.java | 76 + .../viatra/runtime/api/IMatchUpdateListener.java | 37 + .../runtime/api/IModelConnectorTypeEnum.java | 18 + .../refinery/viatra/runtime/api/IPatternMatch.java | 107 ++ .../refinery/viatra/runtime/api/IQueryGroup.java | 46 + .../viatra/runtime/api/IQuerySpecification.java | 87 + .../viatra/runtime/api/IRunOnceQueryEngine.java | 68 + .../viatra/runtime/api/LazyLoadingQueryGroup.java | 64 + .../viatra/runtime/api/MatchUpdateAdapter.java | 105 ++ .../viatra/runtime/api/PackageBasedQueryGroup.java | 133 ++ .../viatra/runtime/api/ViatraQueryEngine.java | 152 ++ .../ViatraQueryEngineInitializationListener.java | 26 + .../api/ViatraQueryEngineLifecycleListener.java | 52 + .../runtime/api/ViatraQueryEngineManager.java | 196 +++ .../runtime/api/ViatraQueryEngineOptions.java | 293 ++++ .../viatra/runtime/api/ViatraQueryMatcher.java | 258 +++ .../api/ViatraQueryModelUpdateListener.java | 55 + .../runtime/api/impl/BaseGeneratedEMFPQuery.java | 110 ++ .../impl/BaseGeneratedEMFQuerySpecification.java | 40 + ...tedEMFQuerySpecificationWithGenericMatcher.java | 58 + .../api/impl/BaseGeneratedPatternGroup.java | 31 + .../viatra/runtime/api/impl/BaseMatcher.java | 350 ++++ .../viatra/runtime/api/impl/BasePatternMatch.java | 115 ++ .../viatra/runtime/api/impl/BaseQueryGroup.java | 33 + .../runtime/api/impl/BaseQuerySpecification.java | 147 ++ .../runtime/api/impl/RunOnceQueryEngine.java | 140 ++ .../viatra/runtime/api/scope/IBaseIndex.java | 91 ++ .../viatra/runtime/api/scope/IEngineContext.java | 49 + .../runtime/api/scope/IIndexingErrorListener.java | 25 + .../runtime/api/scope/IInstanceObserver.java | 21 + .../viatra/runtime/api/scope/QueryScope.java | 33 + .../api/scope/ViatraBaseIndexChangeListener.java | 34 + .../runtime/emf/DynamicEMFQueryRuntimeContext.java | 47 + .../viatra/runtime/emf/EMFBaseIndexWrapper.java | 160 ++ .../viatra/runtime/emf/EMFEngineContext.java | 110 ++ .../viatra/runtime/emf/EMFQueryMetaContext.java | 405 +++++ .../viatra/runtime/emf/EMFQueryRuntimeContext.java | 839 ++++++++++ .../refinery/viatra/runtime/emf/EMFScope.java | 199 +++ .../emf/helper/ViatraQueryRuntimeHelper.java | 161 ++ .../viatra/runtime/emf/types/BaseEMFTypeKey.java | 34 + .../runtime/emf/types/EClassExactInstancesKey.java | 51 + .../emf/types/EClassTransitiveInstancesKey.java | 47 + .../EClassUnscopedTransitiveInstancesKey.java | 46 + .../runtime/emf/types/EDataTypeInSlotsKey.java | 48 + .../emf/types/EStructuralFeatureInstancesKey.java | 48 + .../runtime/exception/ViatraQueryException.java | 71 + .../runtime/extensibility/IQueryGroupProvider.java | 40 + .../extensibility/IQuerySpecificationProvider.java | 36 + .../extensibility/PQueryExtensionFactory.java | 33 + .../extensibility/SingletonExtensionFactory.java | 62 + .../extensibility/SingletonQueryGroupProvider.java | 46 + .../SingletonQuerySpecificationProvider.java | 42 + .../extensibility/ViatraQueryRuntimeConstants.java | 27 + .../ExtensionBasedSurrogateQueryLoader.java | 148 ++ .../ExtensionBasedSystemDefaultBackendLoader.java | 60 + .../internal/apiimpl/EngineContextFactory.java | 24 + .../internal/apiimpl/QueryResultWrapper.java | 32 + .../internal/apiimpl/ViatraQueryEngineImpl.java | 705 ++++++++ .../runtime/internal/engine/LifecycleProvider.java | 138 ++ .../runtime/internal/engine/ListenerContainer.java | 43 + .../internal/engine/ModelUpdateProvider.java | 214 +++ .../ExtensionBasedQuerySpecificationLoader.java | 303 ++++ .../runtime/registry/IConnectorListener.java | 40 + .../runtime/registry/IDefaultRegistryView.java | 37 + .../registry/IQuerySpecificationRegistry.java | 74 + .../IQuerySpecificationRegistryChangeListener.java | 35 + .../registry/IQuerySpecificationRegistryEntry.java | 48 + .../runtime/registry/IRegistrySourceConnector.java | 50 + .../viatra/runtime/registry/IRegistryView.java | 72 + .../runtime/registry/IRegistryViewFactory.java | 34 + .../runtime/registry/IRegistryViewFilter.java | 33 + .../registry/QuerySpecificationRegistry.java | 112 ++ .../connector/AbstractRegistrySourceConnector.java | 81 + .../QueryGroupProviderSourceConnector.java | 80 + .../connector/SpecificationMapSourceConnector.java | 147 ++ .../registry/data/QuerySpecificationStore.java | 38 + .../runtime/registry/data/RegistryEntryImpl.java | 81 + .../runtime/registry/data/RegistrySourceImpl.java | 73 + .../registry/impl/FilteringRegistryView.java | 43 + .../runtime/registry/impl/GlobalRegistryView.java | 65 + .../impl/QuerySpecificationRegistryImpl.java | 177 ++ .../registry/impl/RegistryChangeMultiplexer.java | 58 + .../registry/view/AbstractRegistryView.java | 150 ++ .../viatra/runtime/tabular/EcoreIndexHost.java | 166 ++ .../runtime/tabular/TabularEngineContext.java | 107 ++ .../viatra/runtime/tabular/TabularIndexHost.java | 145 ++ .../runtime/util/ViatraQueryLoggingUtil.java | 72 + 741 files changed, 74782 insertions(+), 1892 deletions(-) create mode 100644 LICENSES/LicenseRef-EPL-Steward.txt delete mode 100644 subprojects/store-query-viatra/src/main/java/org/eclipse/viatra/query/runtime/rete/network/RefineryConnectionFactory.java delete mode 100644 subprojects/store-query-viatra/src/main/java/org/eclipse/viatra/query/runtime/rete/network/RefineryNodeFactory.java delete mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/IndexerUtils.java delete mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RepresentativeElectionConstraint.java delete mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/rewriter/RefineryPBodyCopier.java delete mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/rewriter/RefineryPBodyNormalizer.java delete mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/rewriter/RefinerySurrogateQueryRewriter.java delete mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/RefineryReteBackendFactory.java delete mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/RefineryReteEngine.java delete mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/RefineryReteRecipeCompiler.java delete mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/network/RefineryConnectionFactoryExtensions.java delete mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/network/RefineryNodeFactoryExtensions.java delete mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/network/RepresentativeElectionAlgorithm.java delete mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/network/RepresentativeElectionNode.java delete mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/network/StronglyConnectedComponentAlgorithm.java delete mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/network/WeaklyConnectedComponentAlgorithm.java delete mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/recipe/RefineryRecipesFactory.java delete mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/recipe/RefineryRecipesFactoryImpl.java delete mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/recipe/RefineryRecipesPackage.java delete mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/recipe/RefineryRecipesPackageImpl.java delete mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/recipe/RepresentativeElectionRecipe.java delete mode 100644 subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/recipe/RepresentativeElectionRecipeImpl.java create mode 100644 subprojects/viatra-runtime-base-itc/about.html create mode 100644 subprojects/viatra-runtime-base-itc/build.gradle.kts create mode 100644 subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/counting/CountingAlg.java create mode 100644 subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/counting/CountingTcRelation.java create mode 100644 subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/dred/DRedAlg.java create mode 100644 subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/dred/DRedTcRelation.java create mode 100644 subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/fw/FloydWarshallAlg.java create mode 100644 subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/incscc/CountingListener.java create mode 100644 subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/incscc/IncSCCAlg.java create mode 100644 subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/DFSPathFinder.java create mode 100644 subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/Edge.java create mode 100644 subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/GraphHelper.java create mode 100644 subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/IGraphPathFinder.java create mode 100644 subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/ITcRelation.java create mode 100644 subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/Tuple.java create mode 100644 subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/bfs/BFS.java create mode 100644 subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/dfs/DFSAlg.java create mode 100644 subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/scc/PKAlg.java create mode 100644 subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/scc/SCC.java create mode 100644 subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/scc/SCCProperty.java create mode 100644 subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/scc/SCCResult.java create mode 100644 subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/topsort/TopologicalSorting.java create mode 100644 subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/representative/RepresentativeElectionAlgorithm.java create mode 100644 subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/representative/RepresentativeObserver.java create mode 100644 subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/representative/StronglyConnectedComponentAlgorithm.java create mode 100644 subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/representative/WeaklyConnectedComponentAlgorithm.java create mode 100644 subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/util/CollectionHelper.java create mode 100644 subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/graphimpl/DotGenerator.java create mode 100644 subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/graphimpl/Graph.java create mode 100644 subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/igraph/IBiDirectionalGraphDataSource.java create mode 100644 subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/igraph/IBiDirectionalWrapper.java create mode 100644 subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/igraph/IGraphDataSource.java create mode 100644 subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/igraph/IGraphObserver.java create mode 100644 subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/igraph/ITcDataSource.java create mode 100644 subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/igraph/ITcObserver.java create mode 100644 subprojects/viatra-runtime-base/about.html create mode 100644 subprojects/viatra-runtime-base/build.gradle.kts create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/ViatraBasePlugin.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/BaseIndexOptions.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/DataTypeListener.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/EMFBaseIndexChangeListener.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/FeatureListener.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IEClassifierProcessor.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IEMFIndexingErrorListener.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IQueryResultSetter.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IQueryResultUpdateListener.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IStructuralFeatureInstanceProcessor.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IndexingLevel.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/InstanceListener.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/LightweightEObjectObserver.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/LightweightEObjectObserverAdapter.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/NavigationHelper.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/QueryResultAssociativeStore.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/QueryResultMap.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/TransitiveClosureHelper.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/ViatraBaseFactory.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/filters/IBaseIndexFeatureFilter.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/filters/IBaseIndexObjectFilter.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/filters/IBaseIndexResourceFilter.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/filters/SimpleBaseIndexFilter.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/profiler/BaseIndexProfiler.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/profiler/ProfilerMode.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/comprehension/EMFModelComprehension.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/comprehension/EMFVisitor.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/comprehension/WellbehavingDerivedFeatureRegistry.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/AbstractBaseIndexStore.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/EMFBaseIndexInstanceStore.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/EMFBaseIndexMetaStore.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/EMFBaseIndexStatisticsStore.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/EMFDataSource.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperContentAdapter.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperImpl.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperSetting.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperType.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperVisitor.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/TransitiveClosureHelperImpl.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/profiler/ProfilingNavigationHelperContentAdapter.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/exception/ViatraBaseException.java create mode 100644 subprojects/viatra-runtime-localsearch/about.html create mode 100644 subprojects/viatra-runtime-localsearch/build.gradle.kts create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/ExecutionLoggerAdapter.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/MatchingFrame.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/exceptions/LocalSearchException.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/CallWithAdornment.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/ILocalSearchAdaptable.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/ILocalSearchAdapter.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/ISearchContext.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/LocalSearchMatcher.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/MatcherReference.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/AbstractLocalSearchResultProvider.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/AllValidAdornments.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/DontFlattenDisjunctive.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/DontFlattenIncrementalPredicate.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/GenericLocalSearchResultProvider.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/IAdornmentProvider.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LazyPlanningAdornments.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchBackend.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchBackendFactoryProvider.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchEMFBackendFactory.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchGenericBackendFactory.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchGenericBackendFactoryProvider.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchHintOptions.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchHints.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchResultProvider.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/CheckOperationExecutor.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/ExtendOperationExecutor.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/IIteratingSearchOperation.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/IPatternMatcherOperation.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/ISearchOperation.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/MatchingFrameValueProvider.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/AggregatorCheck.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/BinaryTransitiveClosureCheck.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/CheckConstant.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/CheckPositivePatternCall.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/ContainmentCheck.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/CountCheck.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/ExpressionCheck.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/ExpressionEvalCheck.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/InequalityCheck.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/InstanceOfClassCheck.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/InstanceOfDataTypeCheck.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/InstanceOfJavaClassCheck.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/NACOperation.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/StructuralFeatureCheck.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/nobase/ScopeCheck.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/AggregatorExtend.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/CountOperation.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExpressionEval.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExtendBinaryTransitiveClosure.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExtendConstant.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExtendPositivePatternCall.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExtendToEStructuralFeatureSource.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExtendToEStructuralFeatureTarget.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/IterateOverChildren.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/IterateOverContainers.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/IterateOverEClassInstances.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/IterateOverEDatatypeInstances.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/IterateOverEStructuralFeatureInstances.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/SingleValueExtendOperationExecutor.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/nobase/AbstractIteratingExtendOperationExecutor.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/nobase/ExtendToEStructuralFeatureSource.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/nobase/IterateOverEClassInstances.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/nobase/IterateOverEDatatypeInstances.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/generic/GenericTypeCheck.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/generic/GenericTypeExtend.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/generic/GenericTypeExtendSingleValue.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/util/CallInformation.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/IPlanDescriptor.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/IPlanProvider.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/PlanDescriptor.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/SearchPlan.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/SearchPlanExecutor.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/SearchPlanForBody.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/SimplePlanProvider.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/ILocalSearchPlanner.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/ISearchPlanCodeGenerator.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/LocalSearchPlanner.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/LocalSearchRuntimeBasedStrategy.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/PConstraintCategory.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/PConstraintInfo.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/PConstraintInfoInferrer.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/PlanState.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/compiler/AbstractOperationCompiler.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/compiler/EMFOperationCompiler.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/compiler/GenericOperationCompiler.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/compiler/IOperationCompiler.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/IConstraintEvaluationContext.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/ICostFunction.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/impl/HybridMatcherConstraintCostFunction.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/impl/IndexerBasedConstraintCostFunction.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/impl/StatisticsBasedConstraintCostFunction.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/impl/VariableBindingBasedCostFunction.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/util/CompilerHelper.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/util/OperationCostComparator.java create mode 100644 subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/profiler/LocalSearchProfilerAdapter.java create mode 100644 subprojects/viatra-runtime-matchers/about.html create mode 100644 subprojects/viatra-runtime-matchers/build.gradle.kts create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/ViatraQueryRuntimeException.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/AverageAccumulator.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/DoubleAverageOperator.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/DoubleSumOperator.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/ExtremumOperator.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/IntegerAverageOperator.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/IntegerSumOperator.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/LongAverageOperator.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/LongSumOperator.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/avg.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/count.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/max.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/min.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/sum.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/algorithms/OrderedIterableMerge.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/algorithms/UnionFind.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/algorithms/UnionFindNodeProperty.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/CommonQueryHintOptions.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/ICallDelegationStrategy.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IMatcherCapability.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryBackend.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryBackendFactory.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryBackendFactoryProvider.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryBackendHintProvider.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryResultProvider.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IUpdateable.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/QueryEvaluationHint.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/QueryHintOption.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/ResultProviderRequestor.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/AbstractQueryMetaContext.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/AbstractQueryRuntimeContext.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/IInputKey.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/IPosetComparator.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryBackendContext.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryCacheContext.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryMetaContext.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryResultProviderAccess.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryRuntimeContext.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryRuntimeContextListener.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/IndexingService.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/InputKeyImplication.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/common/BaseInputKeyWrapper.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/common/JavaTransitiveInstancesKey.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/surrogate/SurrogateQueryRegistry.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/AbstractTrivialMaskedMemory.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/DefaultMaskedTupleMemory.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/IdentityMaskedTupleMemory.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/MaskedTupleMemory.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/NullaryMaskedTupleMemory.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/UnaryMaskedTupleMemory.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/AbstractTimelyMaskedMemory.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/AbstractTimelyTrivialMaskedMemory.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/TimelyDefaultMaskedTupleMemory.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/TimelyIdentityMaskedTupleMemory.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/TimelyNullaryMaskedTupleMemory.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/TimelyUnaryMaskedTupleMemory.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/IOperationCompiler.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/IQueryPlannerStrategy.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/QueryProcessingException.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/SubPlan.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/SubPlanFactory.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/helpers/BuildHelper.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/helpers/FunctionalDependencyHelper.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/helpers/StatisticsHelper.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/helpers/TypeHelper.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PApply.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PEnumerate.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PJoin.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/POperation.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PProject.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PStart.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/BasePConstraint.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/DeferredPConstraint.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/EnumerablePConstraint.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IExpressionEvaluator.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IMultiQueryReference.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IQueryReference.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IRelationEvaluator.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/ITypeConstraint.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/ITypeInfoProviderConstraint.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IValueProvider.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/InitializablePQuery.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/KeyedEnumerablePConstraint.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/PBody.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/PConstraint.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/PTraceable.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/PVariable.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/TypeJudgement.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/VariableDeferredPConstraint.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/AbstractMemorylessAggregationOperator.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/AggregatorType.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/BoundAggregator.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/IAggregatorFactory.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/IMultisetAggregationOperator.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/analysis/QueryAnalyzer.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/annotations/PAnnotation.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/annotations/ParameterReference.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/AggregatorConstraint.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/BaseTypeSafeConstraint.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/Equality.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/ExportedParameter.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/ExpressionEvaluation.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/Inequality.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/NegativePatternCall.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/PatternCallBasedDeferred.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/PatternMatchCounter.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/RelationEvaluation.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/TypeFilterConstraint.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/AbstractTransitiveClosure.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/BinaryReflexiveTransitiveClosure.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/BinaryTransitiveClosure.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/Connectivity.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/ConstantValue.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/PositivePatternCall.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/RepresentativeElectionConstraint.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/TypeConstraint.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/BasePQuery.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PDisjunction.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PParameter.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PParameterDirection.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PProblem.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PQueries.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PQuery.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PQueryHeader.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PVisibility.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/QueryInitializationException.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/AbstractRewriterTraceSource.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/ConstraintRemovalReason.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/DefaultFlattenCallPredicate.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/FlattenerCopier.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IConstraintFilter.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IDerivativeModificationReason.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IFlattenCallPredicate.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IPTraceableTraceProvider.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IRewriterTraceCollector.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IVariableRenamer.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/MappingTraceCollector.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/NeverFlattenCallPredicate.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/NopTraceCollector.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PBodyCopier.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PBodyNormalizer.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PDisjunctionRewriter.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PDisjunctionRewriterCacher.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PQueryFlattener.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/RewriterException.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/SurrogateQueryRewriter.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/VariableMappingExpressionEvaluatorWrapper.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/IStorageBackend.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/SimpleLocalStorageBackend.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/SimpleRuntimeContext.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/TabularRuntimeContext.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/AbstractIndexTable.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/DefaultIndexTable.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/DisjointUnionTable.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/IIndexTable.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/ITableContext.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/ITableWriterBinary.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/ITableWriterGeneric.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/ITableWriterUnary.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/SimpleBinaryTable.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/SimpleUnaryTable.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/AbstractTuple.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/BaseFlatTuple.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/BaseLeftInheritanceTuple.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple0.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple1.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple2.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple3.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple4.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/IModifiableTuple.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/ITuple.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/LeftInheritanceTuple.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/LeftInheritanceTuple1.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/LeftInheritanceTuple2.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/LeftInheritanceTuple3.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/LeftInheritanceTuple4.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/MaskedTuple.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/Tuple.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/TupleMask.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/TupleMask0.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/TupleMaskIdentity.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/TupleValueProvider.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/Tuples.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/VolatileMaskedTuple.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/VolatileModifiableMaskedTuple.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/VolatileTuple.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/Accuracy.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/Clearable.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/CollectionsFactory.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/Direction.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsBagMemory.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsDeltaBag.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsFactory.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsLongMultiset.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsLongSetMemory.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsMultiLookup.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsMultiset.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsSetMemory.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/EmptyMemory.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/ICache.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/IDeltaBag.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMemory.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMemoryView.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMultiLookup.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMultiLookupAbstract.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMultiset.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/IProvider.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/ISetMemory.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/MapBackedMemoryView.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/MarkedMemory.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/MemoryViewBackedMapView.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/Preconditions.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/PurgableCache.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/Sets.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/Signed.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/SingletonInstanceProvider.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/SingletonMemoryView.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/TimelyMemory.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/resumable/MaskedResumable.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/resumable/Resumable.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/resumable/UnmaskedResumable.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/CompactTimeline.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/Diff.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/SingletonTimeline.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/Timeline.java create mode 100644 subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/Timelines.java create mode 100644 subprojects/viatra-runtime-rete-recipes/META-INF/MANIFEST.MF create mode 100644 subprojects/viatra-runtime-rete-recipes/META-INF/MANIFEST.MF.license create mode 100644 subprojects/viatra-runtime-rete-recipes/about.html create mode 100644 subprojects/viatra-runtime-rete-recipes/build.gradle.kts create mode 100644 subprojects/viatra-runtime-rete-recipes/build.properties create mode 100644 subprojects/viatra-runtime-rete-recipes/plugin.properties create mode 100644 subprojects/viatra-runtime-rete-recipes/plugin.xml create mode 100644 subprojects/viatra-runtime-rete-recipes/src/main/java/tools/refinery/viatra/runtime/rete/recipes/GenerateReteRecipes.mwe2 create mode 100644 subprojects/viatra-runtime-rete-recipes/src/main/java/tools/refinery/viatra/runtime/rete/recipes/helper/RecipeRecognizer.java create mode 100644 subprojects/viatra-runtime-rete-recipes/src/main/java/tools/refinery/viatra/runtime/rete/recipes/helper/RecipesHelper.java create mode 100644 subprojects/viatra-runtime-rete-recipes/src/main/resources/model/recipes.ecore create mode 100644 subprojects/viatra-runtime-rete-recipes/src/main/resources/model/recipes.ecore.license create mode 100644 subprojects/viatra-runtime-rete-recipes/src/main/resources/model/rete-recipes.genmodel create mode 100644 subprojects/viatra-runtime-rete-recipes/src/main/resources/model/rete-recipes.genmodel.license create mode 100644 subprojects/viatra-runtime-rete/about.html create mode 100644 subprojects/viatra-runtime-rete/build.gradle.kts create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/AbstractColumnAggregatorNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/ColumnAggregatorNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/CountNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/GroupedMap.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/GroupedSet.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/IAggregatorNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/IndexerBasedAggregatorNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FaithfulParallelTimelyColumnAggregatorNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FaithfulSequentialTimelyColumnAggregatorNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FaithfulTimelyColumnAggregatorNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FirstOnlyParallelTimelyColumnAggregatorNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FirstOnlySequentialTimelyColumnAggregatorNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FirstOnlyTimelyColumnAggregatorNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/Disconnectable.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/ExternalInputEnumeratorNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/ExternalInputStatelessFilterNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/InputConnector.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/ReteBoundary.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/RetePatternBuildException.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/basiclinear/BasicLinearLayout.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/basiclinear/OrderingHeuristics.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/plancompiler/CompilerHelper.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/plancompiler/RecursionCutoffPoint.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/plancompiler/ReteRecipeCompiler.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/quasitree/JoinCandidate.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/quasitree/JoinOrderingHeuristics.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/quasitree/QuasiTreeLayout.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/quasitree/TieBreaker.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/AbstractEvaluatorNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/EvaluatorCore.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/IEvaluatorNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/MemorylessEvaluatorNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/OutputCachingEvaluatorNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/RelationEvaluatorNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/DefaultIndexerListener.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/DualInputNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/ExistenceNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/GenericProjectionIndexer.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/IdentityIndexer.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/Indexer.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/IndexerListener.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/IndexerWithMemory.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/IterableIndexer.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/JoinNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/MemoryIdentityIndexer.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/MemoryNullIndexer.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/NullIndexer.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/OnetimeIndexer.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/ProjectionIndexer.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/SpecializedProjectionIndexer.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/StandardIndexer.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/TransitiveClosureNodeIndexer.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/timely/TimelyMemoryIdentityIndexer.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/timely/TimelyMemoryNullIndexer.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/DRedReteBackendFactory.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/HintConfigurator.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/IncrementalMatcherCapability.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/ReteBackendFactory.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/ReteBackendFactoryProvider.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/ReteEngine.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/RetePatternMatcher.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/TimelyConfiguration.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/TimelyReteBackendFactory.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/Bag.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/ConstantNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/DefaultDeltaMonitor.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/DeltaMonitor.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/SimpleReceiver.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/BaseNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ConnectionFactory.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/IGroupable.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Network.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/NetworkStructureChangeSensitiveNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Node.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/NodeFactory.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/NodeProvisioner.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/PosetAwareReceiver.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ProductionNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Receiver.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/RederivableNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ReinitializedNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ReteContainer.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/StandardNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Supplier.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Tunnel.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/UpdateMessage.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/CommunicationGroup.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/CommunicationTracker.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/MessageSelector.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/NodeComparator.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/PhasedSelector.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/Timestamp.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timeless/RecursiveCommunicationGroup.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timeless/SingletonCommunicationGroup.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timeless/TimelessCommunicationTracker.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/ResumableNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimelyCommunicationGroup.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimelyCommunicationTracker.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimelyIndexerListenerProxy.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimelyMailboxProxy.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimestampTransformation.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/delayed/DelayedCommand.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/delayed/DelayedConnectCommand.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/delayed/DelayedDisconnectCommand.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/indexer/DefaultMessageIndexer.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/indexer/GroupBasedMessageIndexer.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/indexer/MessageIndexer.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/AdaptableMailbox.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/FallThroughCapableMailbox.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/Mailbox.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/MessageIndexerFactory.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/AbstractUpdateSplittingMailbox.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/BehaviorChangingMailbox.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/DefaultMailbox.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/PosetAwareMailbox.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/UpdateSplittingMailbox.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timely/TimelyMailbox.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/remote/Address.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/remote/RemoteReceiver.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/remote/RemoteSupplier.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/AbstractUniquenessEnforcerNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/CallbackNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/DefaultProductionNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/DiscriminatorBucketNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/DiscriminatorDispatcherNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/EqualityFilterNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/FilterNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/InequalityFilterNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/RepresentativeElectionNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/SingleInputNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TimelyProductionNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TimelyUniquenessEnforcerNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TransformerNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TransitiveClosureNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TransparentNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TrimmerNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/UniquenessEnforcerNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/ValueBinderFilterNode.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/ActiveNodeConflictTrace.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/CompiledQuery.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/CompiledSubPlan.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/ParameterProjectionTrace.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/PatternTraceInfo.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/PlanningTrace.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/RecipeTraceInfo.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/TraceInfo.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/UserRequestTrace.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/util/LexicographicComparator.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/util/Options.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/util/OrderingCompareAgent.java create mode 100644 subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/util/ReteHintOptions.java create mode 100644 subprojects/viatra-runtime/about.html create mode 100644 subprojects/viatra-runtime/build.gradle.kts create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/IExtensions.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/ViatraQueryRuntimePlugin.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/AdvancedViatraQueryEngine.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/GenericPatternMatch.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/GenericPatternMatcher.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/GenericQueryGroup.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/GenericQuerySpecification.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IMatchUpdateListener.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IModelConnectorTypeEnum.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IPatternMatch.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IQueryGroup.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IQuerySpecification.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IRunOnceQueryEngine.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/LazyLoadingQueryGroup.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/MatchUpdateAdapter.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/PackageBasedQueryGroup.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngine.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngineInitializationListener.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngineLifecycleListener.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngineManager.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngineOptions.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryMatcher.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryModelUpdateListener.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseGeneratedEMFPQuery.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseGeneratedEMFQuerySpecification.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseGeneratedEMFQuerySpecificationWithGenericMatcher.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseGeneratedPatternGroup.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseMatcher.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BasePatternMatch.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseQueryGroup.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseQuerySpecification.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/RunOnceQueryEngine.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/IBaseIndex.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/IEngineContext.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/IIndexingErrorListener.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/IInstanceObserver.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/QueryScope.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/ViatraBaseIndexChangeListener.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/DynamicEMFQueryRuntimeContext.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/EMFBaseIndexWrapper.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/EMFEngineContext.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/EMFQueryMetaContext.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/EMFQueryRuntimeContext.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/EMFScope.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/helper/ViatraQueryRuntimeHelper.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/types/BaseEMFTypeKey.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/types/EClassExactInstancesKey.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/types/EClassTransitiveInstancesKey.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/types/EClassUnscopedTransitiveInstancesKey.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/types/EDataTypeInSlotsKey.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/types/EStructuralFeatureInstancesKey.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/exception/ViatraQueryException.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/extensibility/IQueryGroupProvider.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/extensibility/IQuerySpecificationProvider.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/extensibility/PQueryExtensionFactory.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/extensibility/SingletonExtensionFactory.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/extensibility/SingletonQueryGroupProvider.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/extensibility/SingletonQuerySpecificationProvider.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/extensibility/ViatraQueryRuntimeConstants.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/ExtensionBasedSurrogateQueryLoader.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/ExtensionBasedSystemDefaultBackendLoader.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/apiimpl/EngineContextFactory.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/apiimpl/QueryResultWrapper.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/apiimpl/ViatraQueryEngineImpl.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/engine/LifecycleProvider.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/engine/ListenerContainer.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/engine/ModelUpdateProvider.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/ExtensionBasedQuerySpecificationLoader.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/IConnectorListener.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/IDefaultRegistryView.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/IQuerySpecificationRegistry.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/IQuerySpecificationRegistryChangeListener.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/IQuerySpecificationRegistryEntry.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/IRegistrySourceConnector.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/IRegistryView.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/IRegistryViewFactory.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/IRegistryViewFilter.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/QuerySpecificationRegistry.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/connector/AbstractRegistrySourceConnector.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/connector/QueryGroupProviderSourceConnector.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/connector/SpecificationMapSourceConnector.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/data/QuerySpecificationStore.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/data/RegistryEntryImpl.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/data/RegistrySourceImpl.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/impl/FilteringRegistryView.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/impl/GlobalRegistryView.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/impl/QuerySpecificationRegistryImpl.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/impl/RegistryChangeMultiplexer.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/view/AbstractRegistryView.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/tabular/EcoreIndexHost.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/tabular/TabularEngineContext.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/tabular/TabularIndexHost.java create mode 100644 subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/util/ViatraQueryLoggingUtil.java diff --git a/.editorconfig b/.editorconfig index 7e5e9f16..7bf4c4d5 100644 --- a/.editorconfig +++ b/.editorconfig @@ -25,4 +25,10 @@ indent_style = space indent_size = 2 [libs.versions.toml] -max_line_length = 999 \ No newline at end of file +max_line_length = 999 + +[*.{ecore,genmodel}] +max_line_length = 999 + +[*.mwe2] +max_line_length = 999 diff --git a/LICENSES/LicenseRef-EPL-Steward.txt b/LICENSES/LicenseRef-EPL-Steward.txt new file mode 100644 index 00000000..4b81ef82 --- /dev/null +++ b/LICENSES/LicenseRef-EPL-Steward.txt @@ -0,0 +1 @@ +Everyone is permitted to copy and distribute copies of the Agreement; however, in order to avoid inconsistency, the agreement is copyrighted and may only be modified by the Agreement Steward who reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify the Agreement. diff --git a/buildSrc/src/main/kotlin/tools/refinery/gradle/internal/java-conventions.gradle.kts b/buildSrc/src/main/kotlin/tools/refinery/gradle/internal/java-conventions.gradle.kts index 2d41af11..20c404a0 100644 --- a/buildSrc/src/main/kotlin/tools/refinery/gradle/internal/java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/tools/refinery/gradle/internal/java-conventions.gradle.kts @@ -17,9 +17,6 @@ plugins { repositories { mavenCentral() - maven { - url = uri("https://repo.eclipse.org/content/groups/releases/") - } } // Use log4j-over-slf4j instead of log4j 1.x in the tests. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ffd8e356..0b93663c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,11 +15,13 @@ xtext = "2.32.0.M2" [libraries] jetbrainsAnnotations = { group = "org.jetbrains", name = "annotations", version = "24.0.1" } +eclipse = { group = "org.eclipse.platform", name = "org.eclipse.core.runtime", version = "3.27.0" } eclipseCollections = { group = "org.eclipse.collections", name = "eclipse-collections", version.ref = "eclipseCollections" } eclipseCollections-api = { group = "org.eclipse.collections", name = "eclipse-collections-api", version.ref = "eclipseCollections" } ecore = { group = "org.eclipse.emf", name = "org.eclipse.emf.ecore", version.ref = "ecore" } ecore-xmi = { group = "org.eclipse.emf", name = "org.eclipse.emf.ecore.xmi", version = "2.18.0" } ecore-codegen = { group = "org.eclipse.emf", name = "org.eclipse.emf.codegen.ecore", version.ref = "ecore" } +emf = { group = "org.eclipse.emf", name = "org.eclipse.emf.common", version = "2.28.0" } gradlePlugin-frontend = { group = "org.siouan", name = "frontend-gradle-plugin-jdk11", version = "6.0.0" } gradlePlugin-shadow = { group = "com.github.johnrengelman", name = "shadow", version = "8.1.1" } gradlePlugin-sonarqube = { group = "org.sonarsource.scanner.gradle", name = "sonarqube-gradle-plugin", version = "4.3.0.3225" } @@ -39,10 +41,10 @@ mockito-junit = { group = "org.mockito", name = "mockito-junit-jupiter", version mwe-utils = { group = "org.eclipse.emf", name = "org.eclipse.emf.mwe.utils", version = "1.9.0.M2" } mwe2-launch = { group = "org.eclipse.emf", name = "org.eclipse.emf.mwe2.launch", version.ref = "mwe2" } mwe2-lib = { group = "org.eclipse.emf", name = "org.eclipse.emf.mwe2.lib", version.ref = "mwe2" } +osgi = { group = "org.osgi", name = "org.osgi.framework", version = "1.10.0" } slf4j-api = { group = "org.slf4j", name = "slf4j-api", version.ref = "slf4j" } slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" } slf4j-log4j = { group = "org.slf4j", name = "log4j-over-slf4j", version.ref = "slf4j" } -viatra = { group = "org.eclipse.viatra", name = "viatra-query-runtime", version = "2.7.0" } xtext-bom = { group = "org.eclipse.xtext", name = "xtext-dev-bom", version.ref = "xtext" } xtext-core = { group = "org.eclipse.xtext", name = "org.eclipse.xtext", version.ref = "xtext" } xtext-generator-antlr = { group = "org.eclipse.xtext", name = "xtext-antlr-generator", version = "2.1.1" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 2b1c179d..cf31fc7e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -17,6 +17,13 @@ include( "store-query", "store-query-viatra", "store-reasoning", + "viatra-runtime", + "viatra-runtime-base", + "viatra-runtime-base-itc", + "viatra-runtime-localsearch", + "viatra-runtime-matchers", + "viatra-runtime-rete", + "viatra-runtime-rete-recipes", ) for (project in rootProject.children) { diff --git a/subprojects/language-semantics/build.gradle.kts b/subprojects/language-semantics/build.gradle.kts index 23668f30..22c03985 100644 --- a/subprojects/language-semantics/build.gradle.kts +++ b/subprojects/language-semantics/build.gradle.kts @@ -9,11 +9,11 @@ plugins { } dependencies { - implementation(libs.eclipseCollections) - implementation(libs.eclipseCollections.api) + api(libs.eclipseCollections.api) api(project(":refinery-language")) api(project(":refinery-store")) api(project(":refinery-store-query")) api(project(":refinery-store-reasoning")) + implementation(libs.eclipseCollections) testImplementation(testFixtures(project(":refinery-language"))) } diff --git a/subprojects/store-query-viatra/build.gradle.kts b/subprojects/store-query-viatra/build.gradle.kts index e3a22145..fa1c1da3 100644 --- a/subprojects/store-query-viatra/build.gradle.kts +++ b/subprojects/store-query-viatra/build.gradle.kts @@ -9,7 +9,11 @@ plugins { } dependencies { - implementation(libs.ecore) - api(libs.viatra) + api(project(":refinery-viatra-runtime")) + api(project(":refinery-viatra-runtime-localsearch")) + api(project(":refinery-viatra-runtime-rete")) + api(project(":refinery-viatra-runtime-rete-recipes")) api(project(":refinery-store-query")) + implementation(libs.ecore) + implementation(libs.slf4j.log4j) } diff --git a/subprojects/store-query-viatra/src/main/java/org/eclipse/viatra/query/runtime/rete/network/RefineryConnectionFactory.java b/subprojects/store-query-viatra/src/main/java/org/eclipse/viatra/query/runtime/rete/network/RefineryConnectionFactory.java deleted file mode 100644 index 241c5b58..00000000 --- a/subprojects/store-query-viatra/src/main/java/org/eclipse/viatra/query/runtime/rete/network/RefineryConnectionFactory.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.eclipse.viatra.query.runtime.rete.network; - -import org.eclipse.viatra.query.runtime.rete.traceability.RecipeTraceInfo; -import tools.refinery.store.query.viatra.internal.rete.network.RefineryConnectionFactoryExtensions; - -/** - * This class overrides some RETE connection types from {@link ConnectionFactory}. - *

- * Since {@link ConnectionFactory} is package-private, this class has to be in the - * {@code org.eclipse.viatra.query.runtime.rete.network} package as well. - * However, due to JAR signature verification errors, this class cannot be loaded directly - * and has to be loaded at runtime as a byte array instead. - */ -@SuppressWarnings("unused") -public class RefineryConnectionFactory extends ConnectionFactory { - private final RefineryConnectionFactoryExtensions extensions; - - public RefineryConnectionFactory(ReteContainer reteContainer) { - super(reteContainer); - extensions = new RefineryConnectionFactoryExtensions(reteContainer); - } - - @Override - public void connectToParents(RecipeTraceInfo recipeTrace, Node freshNode) { - if (!extensions.connectToParents(recipeTrace, freshNode)) { - super.connectToParents(recipeTrace, freshNode); - } - } -} diff --git a/subprojects/store-query-viatra/src/main/java/org/eclipse/viatra/query/runtime/rete/network/RefineryNodeFactory.java b/subprojects/store-query-viatra/src/main/java/org/eclipse/viatra/query/runtime/rete/network/RefineryNodeFactory.java deleted file mode 100644 index 1a76e22a..00000000 --- a/subprojects/store-query-viatra/src/main/java/org/eclipse/viatra/query/runtime/rete/network/RefineryNodeFactory.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.eclipse.viatra.query.runtime.rete.network; - -import org.apache.log4j.Logger; -import org.eclipse.viatra.query.runtime.rete.recipes.ReteNodeRecipe; -import org.eclipse.viatra.query.runtime.rete.traceability.TraceInfo; -import tools.refinery.store.query.viatra.internal.rete.network.RefineryNodeFactoryExtensions; - -/** - * This class overrides some RETE node types from {@link NodeFactory}. - *

- * Since {@link NodeFactory} is package-private, this class has to be in the - * {@code org.eclipse.viatra.query.runtime.rete.network} package as well. - * However, due to JAR signature verification errors, this class cannot be loaded directly - * and has to be loaded at runtime as a byte array instead. - */ -@SuppressWarnings("unused") -class RefineryNodeFactory extends NodeFactory { - private final RefineryNodeFactoryExtensions extensions = new RefineryNodeFactoryExtensions(); - - public RefineryNodeFactory(Logger logger) { - super(logger); - } - - @Override - public Supplier createNode(ReteContainer reteContainer, ReteNodeRecipe recipe, TraceInfo... traces) { - var extendedResult = extensions.createNode(reteContainer, recipe, traces); - if (extendedResult != null) { - return extendedResult; - } - return super.createNode(reteContainer, recipe, traces); - } -} 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 66279c94..d31325f1 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 @@ -5,9 +5,9 @@ */ package tools.refinery.store.query.viatra; -import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions; -import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory; -import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.api.ViatraQueryEngineOptions; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactory; +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; import tools.refinery.store.model.ModelStore; import tools.refinery.store.query.ModelQueryBuilder; import tools.refinery.store.query.dnf.AnyQuery; 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 da6d7bd5..588c00d4 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 @@ -5,7 +5,7 @@ */ package tools.refinery.store.query.viatra; -import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions; +import tools.refinery.viatra.runtime.api.ViatraQueryEngineOptions; import tools.refinery.store.model.Model; import tools.refinery.store.query.ModelQueryStoreAdapter; 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 d1a65a89..9303cae6 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 @@ -6,10 +6,10 @@ package tools.refinery.store.query.viatra.internal; import org.apache.log4j.Logger; -import org.eclipse.viatra.query.runtime.api.ViatraQueryEngine; -import org.eclipse.viatra.query.runtime.api.scope.IEngineContext; -import org.eclipse.viatra.query.runtime.api.scope.IIndexingErrorListener; -import org.eclipse.viatra.query.runtime.api.scope.QueryScope; +import tools.refinery.viatra.runtime.api.ViatraQueryEngine; +import tools.refinery.viatra.runtime.api.scope.IEngineContext; +import tools.refinery.viatra.runtime.api.scope.IIndexingErrorListener; +import tools.refinery.viatra.runtime.api.scope.QueryScope; import tools.refinery.store.query.viatra.internal.context.RelationalEngineContext; public class RelationalScope extends QueryScope { 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 e17386e1..f1209f69 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 @@ -5,69 +5,46 @@ */ package tools.refinery.store.query.viatra.internal; -import org.eclipse.viatra.query.runtime.api.AdvancedViatraQueryEngine; -import org.eclipse.viatra.query.runtime.api.GenericQueryGroup; -import org.eclipse.viatra.query.runtime.api.IQuerySpecification; -import org.eclipse.viatra.query.runtime.internal.apiimpl.ViatraQueryEngineImpl; -import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackend; -import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory; import tools.refinery.store.model.Model; import tools.refinery.store.model.ModelListener; -import tools.refinery.store.query.resultset.AnyResultSet; -import tools.refinery.store.query.resultset.EmptyResultSet; -import tools.refinery.store.query.resultset.ResultSet; import tools.refinery.store.query.dnf.AnyQuery; import tools.refinery.store.query.dnf.FunctionalQuery; import tools.refinery.store.query.dnf.Query; import tools.refinery.store.query.dnf.RelationalQuery; +import tools.refinery.store.query.resultset.AnyResultSet; +import tools.refinery.store.query.resultset.EmptyResultSet; +import tools.refinery.store.query.resultset.ResultSet; import tools.refinery.store.query.viatra.ViatraModelQueryAdapter; import tools.refinery.store.query.viatra.internal.matcher.FunctionalViatraMatcher; import tools.refinery.store.query.viatra.internal.matcher.RawPatternMatcher; import tools.refinery.store.query.viatra.internal.matcher.RelationalViatraMatcher; +import tools.refinery.viatra.runtime.api.AdvancedViatraQueryEngine; +import tools.refinery.viatra.runtime.api.GenericQueryGroup; +import tools.refinery.viatra.runtime.api.IQuerySpecification; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter, ModelListener { - private static final String DELAY_MESSAGE_DELIVERY_FIELD_NAME = "delayMessageDelivery"; - private static final MethodHandle SET_UPDATE_PROPAGATION_DELAYED_HANDLE; - private static final String QUERY_BACKENDS_FIELD_NAME = "queryBackends"; - private static final MethodHandle GET_QUERY_BACKENDS_HANDLE; - private final Model model; private final ViatraModelQueryStoreAdapterImpl storeAdapter; - private final ViatraQueryEngineImpl queryEngine; + private final AdvancedViatraQueryEngine queryEngine; private final Map resultSets; private boolean pendingChanges; - static { - try { - var lookup = MethodHandles.privateLookupIn(ViatraQueryEngineImpl.class, MethodHandles.lookup()); - SET_UPDATE_PROPAGATION_DELAYED_HANDLE = lookup.findSetter(ViatraQueryEngineImpl.class, - DELAY_MESSAGE_DELIVERY_FIELD_NAME, Boolean.TYPE); - GET_QUERY_BACKENDS_HANDLE = lookup.findGetter(ViatraQueryEngineImpl.class, QUERY_BACKENDS_FIELD_NAME, - Map.class); - } catch (IllegalAccessException | NoSuchFieldException e) { - throw new IllegalStateException("Cannot access private members of %s" - .formatted(ViatraQueryEngineImpl.class.getName()), e); - } - } - ViatraModelQueryAdapterImpl(Model model, ViatraModelQueryStoreAdapterImpl storeAdapter) { this.model = model; this.storeAdapter = storeAdapter; var scope = new RelationalScope(this); - queryEngine = (ViatraQueryEngineImpl) AdvancedViatraQueryEngine.createUnmanagedEngine(scope, + queryEngine = AdvancedViatraQueryEngine.createUnmanagedEngine(scope, storeAdapter.getEngineOptions()); var querySpecifications = storeAdapter.getQuerySpecifications(); GenericQueryGroup.of( Collections.>unmodifiableCollection(querySpecifications.values()).stream() ).prepare(queryEngine); + queryEngine.flushChanges(); var vacuousQueries = storeAdapter.getVacuousQueries(); resultSets = new LinkedHashMap<>(querySpecifications.size() + vacuousQueries.size()); for (var entry : querySpecifications.entrySet()) { @@ -79,7 +56,6 @@ public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter, Mod resultSets.put(vacuousQuery, new EmptyResultSet<>(this, (Query) vacuousQuery)); } - setUpdatePropagationDelayed(true); model.addListener(this); } @@ -95,30 +71,6 @@ public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter, Mod } } - private void setUpdatePropagationDelayed(boolean value) { - try { - SET_UPDATE_PROPAGATION_DELAYED_HANDLE.invokeExact(queryEngine, value); - } catch (Error e) { - // Fatal JVM errors should not be wrapped. - throw e; - } catch (Throwable e) { - throw new IllegalStateException("Cannot set %s".formatted(DELAY_MESSAGE_DELIVERY_FIELD_NAME), e); - } - } - - private Collection getQueryBackends() { - try { - @SuppressWarnings("unchecked") - var backendMap = (Map) GET_QUERY_BACKENDS_HANDLE.invokeExact(queryEngine); - return backendMap.values(); - } catch (Error e) { - // Fatal JVM errors should not be wrapped. - throw e; - } catch (Throwable e) { - throw new IllegalStateException("Cannot get %s".formatted(QUERY_BACKENDS_FIELD_NAME), e); - } - } - @Override public Model getModel() { return model; @@ -154,20 +106,7 @@ public class ViatraModelQueryAdapterImpl implements ViatraModelQueryAdapter, Mod @Override public void flushChanges() { - if (!queryEngine.isUpdatePropagationDelayed()) { - throw new IllegalStateException("Trying to flush changes while changes are already being flushed"); - } - if (!pendingChanges) { - return; - } - setUpdatePropagationDelayed(false); - try { - for (var queryBackend : getQueryBackends()) { - queryBackend.flushUpdates(); - } - } finally { - setUpdatePropagationDelayed(true); - } + queryEngine.flushChanges(); pendingChanges = false; } 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 bf0708bf..c8aa067c 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 @@ -5,11 +5,6 @@ */ package tools.refinery.store.query.viatra.internal; -import org.eclipse.viatra.query.runtime.api.IQuerySpecification; -import org.eclipse.viatra.query.runtime.api.ViatraQueryEngineOptions; -import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchHintOptions; -import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory; -import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; import tools.refinery.store.adapter.AbstractModelAdapterBuilder; import tools.refinery.store.model.ModelStore; import tools.refinery.store.query.dnf.AnyQuery; @@ -23,7 +18,12 @@ import tools.refinery.store.query.viatra.internal.localsearch.FlatCostFunction; import tools.refinery.store.query.viatra.internal.localsearch.RelationalLocalSearchBackendFactory; import tools.refinery.store.query.viatra.internal.matcher.RawPatternMatcher; import tools.refinery.store.query.viatra.internal.pquery.Dnf2PQuery; -import tools.refinery.store.query.viatra.internal.rete.RefineryReteBackendFactory; +import tools.refinery.viatra.runtime.api.IQuerySpecification; +import tools.refinery.viatra.runtime.api.ViatraQueryEngineOptions; +import tools.refinery.viatra.runtime.localsearch.matcher.integration.LocalSearchHintOptions; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactory; +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.rete.matcher.ReteBackendFactory; import java.util.*; import java.util.function.Function; @@ -41,8 +41,8 @@ public class ViatraModelQueryBuilderImpl extends AbstractModelAdapterBuilderorg.eclipse.viatra.query.runtime.tabular.TabularEngineContext + * Copied from tools.refinery.viatra.runtime.tabular.TabularEngineContext */ public class DummyBaseIndexer implements IBaseIndex { DummyBaseIndexer() { 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 7220f8ca..f7d323ff 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 @@ -5,9 +5,9 @@ */ package tools.refinery.store.query.viatra.internal.context; -import org.eclipse.viatra.query.runtime.api.scope.IBaseIndex; -import org.eclipse.viatra.query.runtime.api.scope.IEngineContext; -import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.api.scope.IBaseIndex; +import tools.refinery.viatra.runtime.api.scope.IEngineContext; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; public class RelationalEngineContext implements IEngineContext { 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 211eacb4..77d86a1c 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 @@ -5,10 +5,10 @@ */ package tools.refinery.store.query.viatra.internal.context; -import org.eclipse.viatra.query.runtime.matchers.context.AbstractQueryMetaContext; -import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; -import org.eclipse.viatra.query.runtime.matchers.context.InputKeyImplication; -import org.eclipse.viatra.query.runtime.matchers.context.common.JavaTransitiveInstancesKey; +import tools.refinery.viatra.runtime.matchers.context.AbstractQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.InputKeyImplication; +import tools.refinery.viatra.runtime.matchers.context.common.JavaTransitiveInstancesKey; import tools.refinery.store.query.viatra.internal.pquery.SymbolViewWrapper; import tools.refinery.store.query.view.AnySymbolView; 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 df80a33e..d1fa5239 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 @@ -5,12 +5,12 @@ */ package tools.refinery.store.query.viatra.internal.context; -import org.eclipse.viatra.query.runtime.matchers.context.*; -import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; -import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; -import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask; -import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; -import org.eclipse.viatra.query.runtime.matchers.util.Accuracy; +import tools.refinery.viatra.runtime.matchers.context.*; +import tools.refinery.viatra.runtime.matchers.tuple.ITuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.Accuracy; import tools.refinery.store.model.Model; import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; import tools.refinery.store.query.viatra.internal.pquery.SymbolViewWrapper; 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 index 37177cbf..fc75198c 100644 --- 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 @@ -8,9 +8,9 @@ *******************************************************************************/ package tools.refinery.store.query.viatra.internal.localsearch; -import org.eclipse.viatra.query.runtime.localsearch.MatchingFrame; -import org.eclipse.viatra.query.runtime.localsearch.matcher.ISearchContext; -import org.eclipse.viatra.query.runtime.localsearch.operations.ISearchOperation.ISearchOperationExecutor; +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation.ISearchOperationExecutor; import java.util.Iterator; @@ -57,7 +57,7 @@ abstract class ExtendOperationExecutor implements ISearchOperationExecutor { } /** - * Fixed version of {@link org.eclipse.viatra.query.runtime.localsearch.operations.ExtendOperationExecutor#execute} + * Fixed version of {@link tools.refinery.viatra.runtime.localsearch.operations.ExtendOperationExecutor#execute} * that handles failed unification of variables correctly. * @param frame The matching frame to extend. * @param context The search context. 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 index 9d48c785..b8b6b159 100644 --- 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 @@ -8,15 +8,15 @@ *******************************************************************************/ package tools.refinery.store.query.viatra.internal.localsearch; -import org.eclipse.viatra.query.runtime.localsearch.MatchingFrame; -import org.eclipse.viatra.query.runtime.localsearch.matcher.ISearchContext; -import org.eclipse.viatra.query.runtime.localsearch.operations.IPatternMatcherOperation; -import org.eclipse.viatra.query.runtime.localsearch.operations.ISearchOperation; -import org.eclipse.viatra.query.runtime.localsearch.operations.util.CallInformation; -import org.eclipse.viatra.query.runtime.matchers.backend.IQueryResultProvider; -import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; -import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask; -import org.eclipse.viatra.query.runtime.matchers.tuple.VolatileModifiableMaskedTuple; +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.IPatternMatcherOperation; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; +import tools.refinery.viatra.runtime.localsearch.operations.util.CallInformation; +import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.VolatileModifiableMaskedTuple; import java.util.Iterator; import java.util.List; 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 index cc906f22..ce2a75bd 100644 --- 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 @@ -5,12 +5,12 @@ */ package tools.refinery.store.query.viatra.internal.localsearch; -import org.eclipse.viatra.query.runtime.localsearch.planner.cost.IConstraintEvaluationContext; -import org.eclipse.viatra.query.runtime.localsearch.planner.cost.impl.StatisticsBasedConstraintCostFunction; -import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; -import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.TypeConstraint; -import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask; -import org.eclipse.viatra.query.runtime.matchers.util.Accuracy; +import tools.refinery.viatra.runtime.localsearch.planner.cost.IConstraintEvaluationContext; +import tools.refinery.viatra.runtime.localsearch.planner.cost.impl.StatisticsBasedConstraintCostFunction; +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.TypeConstraint; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.Accuracy; import java.util.Optional; 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 index 96ac4a72..0ee69b88 100644 --- 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 @@ -8,15 +8,15 @@ *******************************************************************************/ package tools.refinery.store.query.viatra.internal.localsearch; -import org.eclipse.viatra.query.runtime.localsearch.MatchingFrame; -import org.eclipse.viatra.query.runtime.localsearch.matcher.ISearchContext; -import org.eclipse.viatra.query.runtime.localsearch.operations.IIteratingSearchOperation; -import org.eclipse.viatra.query.runtime.localsearch.operations.ISearchOperation; -import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; -import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; -import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask; -import org.eclipse.viatra.query.runtime.matchers.tuple.VolatileMaskedTuple; -import org.eclipse.viatra.query.runtime.matchers.util.Preconditions; +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.IIteratingSearchOperation; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.VolatileMaskedTuple; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; import java.util.*; import java.util.function.Function; 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 index 0c77f587..7d5401c6 100644 --- 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 @@ -5,17 +5,17 @@ */ package tools.refinery.store.query.viatra.internal.localsearch; -import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.AbstractLocalSearchResultProvider; -import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchBackend; -import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchHints; -import org.eclipse.viatra.query.runtime.localsearch.plan.IPlanProvider; -import org.eclipse.viatra.query.runtime.localsearch.plan.SimplePlanProvider; -import org.eclipse.viatra.query.runtime.matchers.backend.IMatcherCapability; -import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackend; -import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory; -import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; -import org.eclipse.viatra.query.runtime.matchers.context.IQueryBackendContext; -import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.localsearch.matcher.integration.AbstractLocalSearchResultProvider; +import tools.refinery.viatra.runtime.localsearch.matcher.integration.LocalSearchBackend; +import tools.refinery.viatra.runtime.localsearch.matcher.integration.LocalSearchHints; +import tools.refinery.viatra.runtime.localsearch.plan.IPlanProvider; +import tools.refinery.viatra.runtime.localsearch.plan.SimplePlanProvider; +import tools.refinery.viatra.runtime.matchers.backend.IMatcherCapability; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackend; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactory; +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; public class RelationalLocalSearchBackendFactory implements IQueryBackendFactory { public static final RelationalLocalSearchBackendFactory INSTANCE = new RelationalLocalSearchBackendFactory(); 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 index da37be14..37d06864 100644 --- 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 @@ -5,14 +5,14 @@ */ package tools.refinery.store.query.viatra.internal.localsearch; -import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.AbstractLocalSearchResultProvider; -import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchBackend; -import org.eclipse.viatra.query.runtime.localsearch.matcher.integration.LocalSearchHints; -import org.eclipse.viatra.query.runtime.localsearch.plan.IPlanProvider; -import org.eclipse.viatra.query.runtime.localsearch.planner.compiler.IOperationCompiler; -import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; -import org.eclipse.viatra.query.runtime.matchers.context.IQueryBackendContext; -import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.localsearch.matcher.integration.AbstractLocalSearchResultProvider; +import tools.refinery.viatra.runtime.localsearch.matcher.integration.LocalSearchBackend; +import tools.refinery.viatra.runtime.localsearch.matcher.integration.LocalSearchHints; +import tools.refinery.viatra.runtime.localsearch.plan.IPlanProvider; +import tools.refinery.viatra.runtime.localsearch.planner.compiler.IOperationCompiler; +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; class RelationalLocalSearchResultProvider extends AbstractLocalSearchResultProvider { public RelationalLocalSearchResultProvider(LocalSearchBackend backend, IQueryBackendContext context, PQuery query, 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 index f76ef486..37f264ae 100644 --- 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 @@ -5,16 +5,16 @@ */ package tools.refinery.store.query.viatra.internal.localsearch; -import org.eclipse.viatra.query.runtime.localsearch.operations.generic.GenericTypeExtendSingleValue; -import org.eclipse.viatra.query.runtime.localsearch.operations.util.CallInformation; -import org.eclipse.viatra.query.runtime.localsearch.planner.compiler.GenericOperationCompiler; -import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; -import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext; -import org.eclipse.viatra.query.runtime.matchers.psystem.PVariable; -import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.PositivePatternCall; -import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.TypeConstraint; -import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; -import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.localsearch.operations.generic.GenericTypeExtendSingleValue; +import tools.refinery.viatra.runtime.localsearch.operations.util.CallInformation; +import tools.refinery.viatra.runtime.localsearch.planner.compiler.GenericOperationCompiler; +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.PositivePatternCall; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.TypeConstraint; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; import java.util.*; @@ -52,7 +52,7 @@ public class RelationalOperationCompiler extends GenericOperationCompiler { unboundVariables.iterator().next())); } else { // Use a fixed version of - // {@code org.eclipse.viatra.query.runtime.localsearch.operations.generic.GenericTypeExtend} that handles + // {@code tools.refinery.viatra.runtime.localsearch.operations.generic.GenericTypeExtend} that handles // failed unification of variables correctly. operations.add(new GenericTypeExtend(inputKey, positions, callMask, indexerMask, unboundVariables)); } @@ -62,7 +62,7 @@ public class RelationalOperationCompiler extends GenericOperationCompiler { protected void createExtend(PositivePatternCall pCall, Map variableMapping) { CallInformation information = CallInformation.create(pCall, variableMapping, variableBindings.get(pCall)); // Use a fixed version of - // {@code org.eclipse.viatra.query.runtime.localsearch.operations.extend.ExtendPositivePatternCall} that handles + // {@code tools.refinery.viatra.runtime.localsearch.operations.extend.ExtendPositivePatternCall} that handles // failed unification of variables correctly. operations.add(new ExtendPositivePatternCall(information)); dependencies.add(information.getCallWithAdornment()); 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 index 99b0a3d8..c4a5a236 100644 --- 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 @@ -5,8 +5,8 @@ */ package tools.refinery.store.query.viatra.internal.matcher; -import org.eclipse.viatra.query.runtime.matchers.backend.IQueryResultProvider; -import org.eclipse.viatra.query.runtime.matchers.backend.IUpdateable; +import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider; +import tools.refinery.viatra.runtime.matchers.backend.IUpdateable; import tools.refinery.store.query.dnf.Query; import tools.refinery.store.query.resultset.AbstractResultSet; import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; 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 index 47efb2aa..44038669 100644 --- 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 @@ -5,7 +5,7 @@ */ package tools.refinery.store.query.viatra.internal.matcher; -import org.eclipse.viatra.query.runtime.rete.index.IterableIndexer; +import tools.refinery.viatra.runtime.rete.index.IterableIndexer; import tools.refinery.store.map.Cursor; import tools.refinery.store.tuple.Tuple; @@ -13,7 +13,7 @@ import java.util.Iterator; class FunctionalCursor implements Cursor { private final IterableIndexer indexer; - private final Iterator iterator; + private final Iterator iterator; private boolean terminated; private Tuple key; private T value; 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 index db4740cd..fb9e4df6 100644 --- 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 @@ -5,10 +5,10 @@ */ package tools.refinery.store.query.viatra.internal.matcher; -import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask; -import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; -import org.eclipse.viatra.query.runtime.rete.index.IterableIndexer; -import org.eclipse.viatra.query.runtime.rete.matcher.RetePatternMatcher; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.rete.index.IterableIndexer; +import tools.refinery.viatra.runtime.rete.matcher.RetePatternMatcher; import tools.refinery.store.map.Cursor; import tools.refinery.store.query.dnf.FunctionalQuery; import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; @@ -17,9 +17,9 @@ import tools.refinery.store.tuple.Tuple; /** * Directly access the tuples inside a VIATRA pattern matcher.

* This class neglects calling - * {@link org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext#wrapTuple(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple)} + * {@link tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext#wrapTuple(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple)} * and - * {@link org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext#unwrapTuple(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple)}, + * {@link tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext#unwrapTuple(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple)}, * because {@link tools.refinery.store.query.viatra.internal.context.RelationalRuntimeContext} provides a trivial * implementation for these methods. * Using this class with any other runtime context may lead to undefined behavior. @@ -37,7 +37,7 @@ public class FunctionalViatraMatcher extends AbstractViatraMatcher { emptyMask = TupleMask.empty(arityWithOutput); omitOutputMask = TupleMask.omit(arity, arityWithOutput); if (backend instanceof RetePatternMatcher reteBackend) { - var maybeIterableOmitOutputIndexer = IndexerUtils.getIndexer(reteBackend, omitOutputMask); + var maybeIterableOmitOutputIndexer = reteBackend.getInternalIndexer(omitOutputMask); if (maybeIterableOmitOutputIndexer instanceof IterableIndexer iterableOmitOutputIndexer) { omitOutputIndexer = iterableOmitOutputIndexer; } else { @@ -76,7 +76,7 @@ public class FunctionalViatraMatcher extends AbstractViatraMatcher { } @Override - public void update(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple updateElement, boolean isInsertion) { + public void update(tools.refinery.viatra.runtime.matchers.tuple.Tuple updateElement, boolean isInsertion) { var key = MatcherUtils.keyToRefineryTuple(updateElement); var value = MatcherUtils.getValue(updateElement); if (isInsertion) { 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 deleted file mode 100644 index 15f00b2d..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/matcher/IndexerUtils.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.query.viatra.internal.matcher; - -import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask; -import org.eclipse.viatra.query.runtime.rete.index.Indexer; -import org.eclipse.viatra.query.runtime.rete.matcher.ReteEngine; -import org.eclipse.viatra.query.runtime.rete.matcher.RetePatternMatcher; -import org.eclipse.viatra.query.runtime.rete.traceability.RecipeTraceInfo; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; - -final class IndexerUtils { - private static final MethodHandle GET_ENGINE_HANDLE; - private static final MethodHandle GET_PRODUCTION_NODE_TRACE_HANDLE; - private static final MethodHandle ACCESS_PROJECTION_HANDLE; - - static { - try { - var lookup = MethodHandles.privateLookupIn(RetePatternMatcher.class, MethodHandles.lookup()); - GET_ENGINE_HANDLE = lookup.findGetter(RetePatternMatcher.class, "engine", ReteEngine.class); - GET_PRODUCTION_NODE_TRACE_HANDLE = lookup.findGetter(RetePatternMatcher.class, "productionNodeTrace", - RecipeTraceInfo.class); - ACCESS_PROJECTION_HANDLE = lookup.findVirtual(ReteEngine.class, "accessProjection", - MethodType.methodType(Indexer.class, RecipeTraceInfo.class, TupleMask.class)); - } catch (IllegalAccessException | NoSuchFieldException | NoSuchMethodException e) { - throw new IllegalStateException("Cannot access private members of %s" - .formatted(RetePatternMatcher.class.getPackageName()), e); - } - } - - private IndexerUtils() { - throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); - } - - public static Indexer getIndexer(RetePatternMatcher backend, TupleMask mask) { - try { - var engine = (ReteEngine) GET_ENGINE_HANDLE.invokeExact(backend); - var trace = (RecipeTraceInfo) GET_PRODUCTION_NODE_TRACE_HANDLE.invokeExact(backend); - return (Indexer) ACCESS_PROJECTION_HANDLE.invokeExact(engine, trace, mask); - } catch (Error e) { - // Fatal JVM errors should not be wrapped. - throw e; - } catch (Throwable e) { - throw new IllegalStateException("Cannot access matcher for mask " + mask, e); - } - } -} 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 index 6e24812a..6cda23cd 100644 --- 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 @@ -5,8 +5,8 @@ */ package tools.refinery.store.query.viatra.internal.matcher; -import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; -import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.tuple.ITuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; import org.jetbrains.annotations.Nullable; import tools.refinery.store.tuple.*; @@ -17,7 +17,7 @@ final class MatcherUtils { throw new IllegalStateException("This is a static utility class and should not be instantiated directly"); } - public static org.eclipse.viatra.query.runtime.matchers.tuple.Tuple toViatraTuple(Tuple refineryTuple) { + public static tools.refinery.viatra.runtime.matchers.tuple.Tuple toViatraTuple(Tuple refineryTuple) { if (refineryTuple instanceof Tuple0) { return Tuples.staticArityFlatTupleOf(); } else if (refineryTuple instanceof Tuple1) { 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 index 5b82c4b7..c0be70ba 100644 --- 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 @@ -5,9 +5,9 @@ */ package tools.refinery.store.query.viatra.internal.matcher; -import org.eclipse.viatra.query.runtime.api.GenericPatternMatcher; -import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification; -import org.eclipse.viatra.query.runtime.matchers.backend.IQueryResultProvider; +import tools.refinery.viatra.runtime.api.GenericPatternMatcher; +import tools.refinery.viatra.runtime.api.GenericQuerySpecification; +import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider; public class RawPatternMatcher extends GenericPatternMatcher { public RawPatternMatcher(GenericQuerySpecification specification) { 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 index 1dc8f5db..53475218 100644 --- 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 @@ -5,7 +5,7 @@ */ package tools.refinery.store.query.viatra.internal.matcher; -import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; +import tools.refinery.viatra.runtime.matchers.tuple.ITuple; import tools.refinery.store.map.Cursor; import tools.refinery.store.tuple.Tuple; 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 index ac95dcc0..da75e864 100644 --- 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 @@ -5,10 +5,10 @@ */ package tools.refinery.store.query.viatra.internal.matcher; -import org.eclipse.viatra.query.runtime.matchers.tuple.TupleMask; -import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; -import org.eclipse.viatra.query.runtime.rete.index.Indexer; -import org.eclipse.viatra.query.runtime.rete.matcher.RetePatternMatcher; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.rete.index.Indexer; +import tools.refinery.viatra.runtime.rete.matcher.RetePatternMatcher; import tools.refinery.store.map.Cursor; import tools.refinery.store.map.Cursors; import tools.refinery.store.query.dnf.RelationalQuery; @@ -18,9 +18,9 @@ import tools.refinery.store.tuple.Tuple; /** * Directly access the tuples inside a VIATRA pattern matcher.

* This class neglects calling - * {@link org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext#wrapTuple(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple)} + * {@link tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext#wrapTuple(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple)} * and - * {@link org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext#unwrapTuple(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple)}, + * {@link tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext#unwrapTuple(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple)}, * because {@link tools.refinery.store.query.viatra.internal.context.RelationalRuntimeContext} provides a trivial * implementation for these methods. * Using this class with any other runtime context may lead to undefined behavior. @@ -37,7 +37,7 @@ public class RelationalViatraMatcher extends AbstractViatraMatcher { emptyMask = TupleMask.empty(arity); identityMask = TupleMask.identity(arity); if (backend instanceof RetePatternMatcher reteBackend) { - emptyMaskIndexer = IndexerUtils.getIndexer(reteBackend, emptyMask); + emptyMaskIndexer = reteBackend.getInternalIndexer(emptyMask); } else { emptyMaskIndexer = null; } @@ -73,7 +73,7 @@ public class RelationalViatraMatcher extends AbstractViatraMatcher { } @Override - public void update(org.eclipse.viatra.query.runtime.matchers.tuple.Tuple updateElement, boolean isInsertion) { + public void update(tools.refinery.viatra.runtime.matchers.tuple.Tuple updateElement, boolean isInsertion) { var key = MatcherUtils.toRefineryTuple(updateElement); notifyChange(key, !isInsertion, isInsertion); } 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 index b0b507fe..093ade96 100644 --- 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 @@ -5,7 +5,7 @@ */ package tools.refinery.store.query.viatra.internal.matcher; -import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; +import tools.refinery.viatra.runtime.matchers.tuple.ITuple; import tools.refinery.store.map.Cursor; import tools.refinery.store.tuple.Tuple; diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/CheckEvaluator.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/CheckEvaluator.java index 5dde41be..ada154d4 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/CheckEvaluator.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/CheckEvaluator.java @@ -5,7 +5,7 @@ */ package tools.refinery.store.query.viatra.internal.pquery; -import org.eclipse.viatra.query.runtime.matchers.psystem.IValueProvider; +import tools.refinery.viatra.runtime.matchers.psystem.IValueProvider; import tools.refinery.store.query.term.Term; class CheckEvaluator extends TermEvaluator { 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 index af3bf32e..492bd054 100644 --- 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 @@ -5,24 +5,6 @@ */ package tools.refinery.store.query.viatra.internal.pquery; -import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory; -import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; -import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; -import org.eclipse.viatra.query.runtime.matchers.psystem.PBody; -import org.eclipse.viatra.query.runtime.matchers.psystem.PVariable; -import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.BoundAggregator; -import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; -import org.eclipse.viatra.query.runtime.matchers.psystem.annotations.PAnnotation; -import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.*; -import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.BinaryTransitiveClosure; -import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.ConstantValue; -import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.PositivePatternCall; -import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.TypeConstraint; -import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameter; -import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameterDirection; -import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery; -import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; -import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; import tools.refinery.store.query.Constraint; import tools.refinery.store.query.dnf.Dnf; import tools.refinery.store.query.dnf.DnfClause; @@ -34,6 +16,22 @@ import tools.refinery.store.query.term.StatelessAggregator; import tools.refinery.store.query.term.Variable; import tools.refinery.store.query.view.AnySymbolView; import tools.refinery.store.util.CycleDetectingMapper; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactory; +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.BoundAggregator; +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; +import tools.refinery.viatra.runtime.matchers.psystem.annotations.PAnnotation; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.*; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.*; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.Connectivity; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameterDirection; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; import java.util.ArrayList; import java.util.HashMap; @@ -42,7 +40,6 @@ import java.util.Map; import java.util.function.Function; public class Dnf2PQuery { - private static final Object P_CONSTRAINT_LOCK = new Object(); private final CycleDetectingMapper mapper = new CycleDetectingMapper<>(Dnf::name, this::doTranslate); private final QueryWrapperFactory wrapperFactory = new QueryWrapperFactory(this); @@ -91,22 +88,17 @@ public class Dnf2PQuery { pQuery.addAnnotation(functionalDependencyAnnotation); } - // The constructor of {@link org.eclipse.viatra.query.runtime.matchers.psystem.BasePConstraint} mutates - // global static state (nextID) without locking. Therefore, we need to synchronize before creating - // any query literals to avoid a data race. - synchronized (P_CONSTRAINT_LOCK) { - for (DnfClause clause : dnfQuery.getClauses()) { - PBody body = new PBody(pQuery); - List parameterExports = new ArrayList<>(); - for (var parameter : dnfQuery.getSymbolicParameters()) { - PVariable pVar = body.getOrCreateVariableByName(parameter.getVariable().getUniqueName()); - parameterExports.add(new ExportedParameter(body, pVar, parameters.get(parameter))); - } - body.setSymbolicParameters(parameterExports); - pQuery.addBody(body); - for (Literal literal : clause.literals()) { - translateLiteral(literal, body); - } + for (DnfClause clause : dnfQuery.getClauses()) { + PBody body = new PBody(pQuery); + List parameterExports = new ArrayList<>(); + for (var parameter : dnfQuery.getSymbolicParameters()) { + PVariable pVar = body.getOrCreateVariableByName(parameter.getVariable().getUniqueName()); + parameterExports.add(new ExportedParameter(body, pVar, parameters.get(parameter))); + } + body.setSymbolicParameters(parameterExports); + pQuery.addBody(body); + for (Literal literal : clause.literals()) { + translateLiteral(literal, body); } } @@ -252,6 +244,10 @@ public class Dnf2PQuery { private void translateRepresentativeElectionLiteral(RepresentativeElectionLiteral literal, PBody body) { var substitution = translateSubstitution(literal.getArguments(), body); var pattern = wrapConstraintWithIdentityArguments(literal.getTarget()); - new RepresentativeElectionConstraint(body, substitution, pattern, literal.getConnectivity()); + var connectivity = switch (literal.getConnectivity()) { + case WEAK -> Connectivity.WEAK; + case STRONG -> Connectivity.STRONG; + }; + new RepresentativeElectionConstraint(body, substitution, pattern, connectivity); } } 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 index 502813e1..d21131e5 100644 --- 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 @@ -5,17 +5,17 @@ */ package tools.refinery.store.query.viatra.internal.pquery; -import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; -import org.eclipse.viatra.query.runtime.matchers.psystem.PBody; -import org.eclipse.viatra.query.runtime.matchers.psystem.PVariable; -import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.ExportedParameter; -import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.PositivePatternCall; -import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.TypeConstraint; -import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameter; -import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery; -import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PVisibility; -import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; -import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.ExportedParameter; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.PositivePatternCall; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.TypeConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PVisibility; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; import tools.refinery.store.query.Constraint; import tools.refinery.store.query.dnf.Dnf; import tools.refinery.store.query.dnf.DnfUtils; 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 255738c5..06644bf2 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 @@ -5,14 +5,14 @@ */ package tools.refinery.store.query.viatra.internal.pquery; -import org.eclipse.viatra.query.runtime.api.GenericQuerySpecification; -import org.eclipse.viatra.query.runtime.api.ViatraQueryEngine; -import org.eclipse.viatra.query.runtime.api.scope.QueryScope; -import org.eclipse.viatra.query.runtime.matchers.psystem.PBody; -import org.eclipse.viatra.query.runtime.matchers.psystem.annotations.PAnnotation; -import org.eclipse.viatra.query.runtime.matchers.psystem.queries.BasePQuery; -import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameter; -import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PVisibility; +import tools.refinery.viatra.runtime.api.GenericQuerySpecification; +import tools.refinery.viatra.runtime.api.ViatraQueryEngine; +import tools.refinery.viatra.runtime.api.scope.QueryScope; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.annotations.PAnnotation; +import tools.refinery.viatra.runtime.matchers.psystem.queries.BasePQuery; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PVisibility; import tools.refinery.store.query.viatra.internal.RelationalScope; import tools.refinery.store.query.viatra.internal.matcher.RawPatternMatcher; diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RepresentativeElectionConstraint.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RepresentativeElectionConstraint.java deleted file mode 100644 index e146213e..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/RepresentativeElectionConstraint.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.query.viatra.internal.pquery; - -import org.eclipse.viatra.query.runtime.matchers.context.IQueryMetaContext; -import org.eclipse.viatra.query.runtime.matchers.psystem.*; -import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.PositivePatternCall; -import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery; -import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; -import tools.refinery.store.query.literal.Connectivity; - -import java.util.Set; - -public class RepresentativeElectionConstraint extends KeyedEnumerablePConstraint - implements IQueryReference, ITypeInfoProviderConstraint { - private final Connectivity connectivity; - - public RepresentativeElectionConstraint(PBody pBody, Tuple variablesTuple, PQuery supplierKey, - Connectivity connectivity) { - super(pBody, variablesTuple, supplierKey); - this.connectivity = connectivity; - } - - public Connectivity getConnectivity() { - return connectivity; - } - - @Override - public PQuery getReferredQuery() { - return supplierKey; - } - - @Override - public Set getImpliedJudgements(IQueryMetaContext context) { - return PositivePatternCall.getTypesImpliedByCall(supplierKey, variablesTuple); - } - - @Override - protected String keyToString() { - return supplierKey.getFullyQualifiedName() + "#representative"; - } -} 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 index 461416f7..ba99cf9a 100644 --- 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 @@ -5,7 +5,7 @@ */ package tools.refinery.store.query.viatra.internal.pquery; -import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; import tools.refinery.store.query.term.StatefulAggregate; import tools.refinery.store.query.term.StatefulAggregator; 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 index 49175d75..bf2c2f4f 100644 --- 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 @@ -5,7 +5,7 @@ */ package tools.refinery.store.query.viatra.internal.pquery; -import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; import tools.refinery.store.query.term.StatelessAggregator; import java.util.stream.Stream; diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/SymbolViewWrapper.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/SymbolViewWrapper.java index a777613e..a774404e 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/SymbolViewWrapper.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/SymbolViewWrapper.java @@ -5,7 +5,7 @@ */ package tools.refinery.store.query.viatra.internal.pquery; -import org.eclipse.viatra.query.runtime.matchers.context.common.BaseInputKeyWrapper; +import tools.refinery.viatra.runtime.matchers.context.common.BaseInputKeyWrapper; import tools.refinery.store.query.view.AnySymbolView; public class SymbolViewWrapper extends BaseInputKeyWrapper { 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 index 1187f57a..5df861a6 100644 --- 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 @@ -5,8 +5,8 @@ */ package tools.refinery.store.query.viatra.internal.pquery; -import org.eclipse.viatra.query.runtime.matchers.psystem.IExpressionEvaluator; -import org.eclipse.viatra.query.runtime.matchers.psystem.IValueProvider; +import tools.refinery.viatra.runtime.matchers.psystem.IExpressionEvaluator; +import tools.refinery.viatra.runtime.matchers.psystem.IValueProvider; import tools.refinery.store.query.term.Term; import tools.refinery.store.query.term.Variable; 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 index 62cb8b3a..b9ae8ab2 100644 --- 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 @@ -5,7 +5,7 @@ */ package tools.refinery.store.query.viatra.internal.pquery; -import org.eclipse.viatra.query.runtime.matchers.psystem.IValueProvider; +import tools.refinery.viatra.runtime.matchers.psystem.IValueProvider; import tools.refinery.store.query.term.DataVariable; import tools.refinery.store.query.valuation.Valuation; diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/rewriter/RefineryPBodyCopier.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/rewriter/RefineryPBodyCopier.java deleted file mode 100644 index a833a37b..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/rewriter/RefineryPBodyCopier.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.query.viatra.internal.pquery.rewriter; - -import org.eclipse.viatra.query.runtime.matchers.psystem.PBody; -import org.eclipse.viatra.query.runtime.matchers.psystem.PConstraint; -import org.eclipse.viatra.query.runtime.matchers.psystem.rewriters.IRewriterTraceCollector; -import org.eclipse.viatra.query.runtime.matchers.psystem.rewriters.PBodyCopier; -import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; -import tools.refinery.store.query.viatra.internal.pquery.RepresentativeElectionConstraint; - -public class RefineryPBodyCopier extends PBodyCopier { - public RefineryPBodyCopier(PBody body, IRewriterTraceCollector traceCollector) { - super(body, traceCollector); - } - - @Override - protected void copyConstraint(PConstraint constraint) { - if (constraint instanceof RepresentativeElectionConstraint representativeElectionConstraint) { - copyRepresentativeElectionConstraint(representativeElectionConstraint); - } else { - super.copyConstraint(constraint); - } - } - - private void copyRepresentativeElectionConstraint(RepresentativeElectionConstraint constraint) { - var mappedVariables = extractMappedVariables(constraint); - var variablesTuple = Tuples.flatTupleOf((Object[]) mappedVariables); - addTrace(constraint, new RepresentativeElectionConstraint(body, variablesTuple, constraint.getReferredQuery(), - constraint.getConnectivity())); - } -} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/rewriter/RefineryPBodyNormalizer.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/rewriter/RefineryPBodyNormalizer.java deleted file mode 100644 index ed85a843..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/rewriter/RefineryPBodyNormalizer.java +++ /dev/null @@ -1,38 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro - * Copyright (c) 2023 The Refinery Authors - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-v20.html. - * SPDX-License-Identifier: EPL-2.0 - *******************************************************************************/ -package tools.refinery.store.query.viatra.internal.pquery.rewriter; - -import org.eclipse.viatra.query.runtime.matchers.context.IQueryMetaContext; -import org.eclipse.viatra.query.runtime.matchers.psystem.PBody; -import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PDisjunction; -import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery; -import org.eclipse.viatra.query.runtime.matchers.psystem.rewriters.PBodyCopier; -import org.eclipse.viatra.query.runtime.matchers.psystem.rewriters.PBodyNormalizer; - -import java.util.LinkedHashSet; -import java.util.Set; - -public class RefineryPBodyNormalizer extends PBodyNormalizer { - public RefineryPBodyNormalizer(IQueryMetaContext context) { - super(context); - } - - @Override - public PDisjunction rewrite(PDisjunction disjunction) { - Set normalizedBodies = new LinkedHashSet<>(); - for (PBody body : disjunction.getBodies()) { - PBodyCopier copier = new RefineryPBodyCopier(body, getTraceCollector()); - PBody modifiedBody = copier.getCopiedBody(); - normalizeBody(modifiedBody); - normalizedBodies.add(modifiedBody); - modifiedBody.setStatus(PQuery.PQueryStatus.OK); - } - return new PDisjunction(normalizedBodies); - } -} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/rewriter/RefinerySurrogateQueryRewriter.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/rewriter/RefinerySurrogateQueryRewriter.java deleted file mode 100644 index dc288ba0..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/pquery/rewriter/RefinerySurrogateQueryRewriter.java +++ /dev/null @@ -1,58 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2010-2015, Zoltan Ujhelyi, Istvan Rath and Daniel Varro - * Copyright (c) 2023 The Refinery Authors - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-v20.html. - * SPDX-License-Identifier: EPL-2.0 - *******************************************************************************/ -package tools.refinery.store.query.viatra.internal.pquery.rewriter; - -import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; -import org.eclipse.viatra.query.runtime.matchers.context.surrogate.SurrogateQueryRegistry; -import org.eclipse.viatra.query.runtime.matchers.psystem.PBody; -import org.eclipse.viatra.query.runtime.matchers.psystem.PVariable; -import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.PositivePatternCall; -import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.TypeConstraint; -import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PDisjunction; -import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery; -import org.eclipse.viatra.query.runtime.matchers.psystem.rewriters.PBodyCopier; -import org.eclipse.viatra.query.runtime.matchers.psystem.rewriters.PDisjunctionRewriter; -import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; -import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; - -import java.util.LinkedHashSet; -import java.util.Set; - -public class RefinerySurrogateQueryRewriter extends PDisjunctionRewriter { - @Override - public PDisjunction rewrite(PDisjunction disjunction) { - Set replacedBodies = new LinkedHashSet<>(); - for (PBody body : disjunction.getBodies()) { - PBodyCopier copier = new RefineryPBodyCopier(body, getTraceCollector()) { - - @Override - protected void copyTypeConstraint(TypeConstraint typeConstraint) { - PVariable[] mappedVariables = extractMappedVariables(typeConstraint); - Tuple variablesTuple = Tuples.flatTupleOf((Object[]) mappedVariables); - final IInputKey supplierKey = typeConstraint.getSupplierKey(); - if (SurrogateQueryRegistry.instance().hasSurrogateQueryFQN(supplierKey)) { - PQuery surrogateQuery = SurrogateQueryRegistry.instance().getSurrogateQuery(supplierKey); - if (surrogateQuery == null) { - throw new IllegalStateException("Surrogate query for feature %s not found" - .formatted(supplierKey.getPrettyPrintableName())); - } - addTrace(typeConstraint, new PositivePatternCall(getCopiedBody(), variablesTuple, - surrogateQuery)); - } else { - addTrace(typeConstraint, new TypeConstraint(getCopiedBody(), variablesTuple, supplierKey)); - } - } - }; - PBody modifiedBody = copier.getCopiedBody(); - replacedBodies.add(modifiedBody); - modifiedBody.setStatus(PQuery.PQueryStatus.OK); - } - return new PDisjunction(replacedBodies); - } -} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/RefineryReteBackendFactory.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/RefineryReteBackendFactory.java deleted file mode 100644 index 517e511a..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/RefineryReteBackendFactory.java +++ /dev/null @@ -1,90 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro - * Copyright (c) 2023 The Refinery Authors - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-v20.html. - * SPDX-License-Identifier: EPL-2.0 - *******************************************************************************/ -package tools.refinery.store.query.viatra.internal.rete; - -import org.eclipse.viatra.query.runtime.matchers.backend.*; -import org.eclipse.viatra.query.runtime.matchers.context.IQueryBackendContext; -import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery; -import org.eclipse.viatra.query.runtime.rete.construction.plancompiler.ReteRecipeCompiler; -import org.eclipse.viatra.query.runtime.rete.matcher.IncrementalMatcherCapability; -import org.eclipse.viatra.query.runtime.rete.matcher.ReteEngine; -import org.eclipse.viatra.query.runtime.rete.matcher.TimelyConfiguration; -import org.eclipse.viatra.query.runtime.rete.util.Options; - -// Singleton implementations follows the VIATRA implementation. -@SuppressWarnings("squid:S6548") -public class RefineryReteBackendFactory implements IQueryBackendFactory { - /** - * EXPERIMENTAL - */ - protected static final int RETE_THREADS = 0; - - /** - * @since 2.0 - */ - public static final RefineryReteBackendFactory INSTANCE = new RefineryReteBackendFactory(); - - /** - * @since 1.5 - */ - @Override - public IQueryBackend create(IQueryBackendContext context) { - return create(context, false, null); - } - - /** - * @since 2.4 - */ - public IQueryBackend create(IQueryBackendContext context, boolean deleteAndReDeriveEvaluation, - TimelyConfiguration timelyConfiguration) { - ReteEngine engine; - engine = new RefineryReteEngine(context, RETE_THREADS, deleteAndReDeriveEvaluation, timelyConfiguration); - IQueryBackendHintProvider hintConfiguration = engine.getHintConfiguration(); - ReteRecipeCompiler compiler = new RefineryReteRecipeCompiler( - Options.builderMethod.layoutStrategy(context, hintConfiguration), context.getLogger(), - context.getRuntimeContext().getMetaContext(), context.getQueryCacheContext(), hintConfiguration, - context.getQueryAnalyzer(), deleteAndReDeriveEvaluation, timelyConfiguration); - engine.setCompiler(compiler); - return engine; - } - - @Override - public Class getBackendClass() { - return ReteEngine.class; - } - - @Override - public int hashCode() { - return RefineryReteBackendFactory.class.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - return obj instanceof RefineryReteBackendFactory; - } - - /** - * @since 1.4 - */ - @Override - public IMatcherCapability calculateRequiredCapability(PQuery query, QueryEvaluationHint hint) { - return new IncrementalMatcherCapability(); - } - - @Override - public boolean isCaching() { - return true; - } -} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/RefineryReteEngine.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/RefineryReteEngine.java deleted file mode 100644 index c088219b..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/RefineryReteEngine.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.query.viatra.internal.rete; - -import org.apache.log4j.Logger; -import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendFactory; -import org.eclipse.viatra.query.runtime.matchers.context.IQueryBackendContext; -import org.eclipse.viatra.query.runtime.rete.matcher.ReteEngine; -import org.eclipse.viatra.query.runtime.rete.matcher.TimelyConfiguration; -import org.eclipse.viatra.query.runtime.rete.network.Network; -import org.eclipse.viatra.query.runtime.rete.network.NodeProvisioner; -import org.eclipse.viatra.query.runtime.rete.network.ReteContainer; - -import java.io.IOException; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; - -public class RefineryReteEngine extends ReteEngine { - private static final MethodHandle REFINERY_NODE_FACTORY_CONSTRUCTOR; - private static final MethodHandle REFINERY_CONNECTION_FACTORY_CONSTRUCTOR; - private static final MethodHandle NETWORK_NODE_FACTORY_SETTER; - private static final MethodHandle RETE_CONTAINER_CONNECTION_FACTORY_SETTER; - private static final MethodHandle NODE_PROVISIONER_NODE_FACTORY_SETTER; - private static final MethodHandle NODE_PROVISIONER_CONNECTION_FACTORY_SETTER; - - static { - MethodHandles.Lookup lookup; - try { - lookup = MethodHandles.privateLookupIn(Network.class, MethodHandles.lookup()); - } catch (IllegalAccessException e) { - throw new IllegalStateException("Cannot create private lookup", e); - } - var refineryNodeFactoryClass = defineClassFromFile(lookup, "RefineryNodeFactory"); - var refinaryConnectionFactoryClass = defineClassFromFile(lookup, "RefineryConnectionFactory"); - try { - REFINERY_NODE_FACTORY_CONSTRUCTOR = lookup.findConstructor(refineryNodeFactoryClass, - MethodType.methodType(Void.TYPE, Logger.class)); - REFINERY_CONNECTION_FACTORY_CONSTRUCTOR = lookup.findConstructor(refinaryConnectionFactoryClass, - MethodType.methodType(Void.TYPE, ReteContainer.class)); - } catch (NoSuchMethodException | IllegalAccessException e) { - throw new IllegalStateException("Cannot get constructor", e); - } - var nodeFactoryClass = refineryNodeFactoryClass.getSuperclass(); - var connectionFactoryClass = refinaryConnectionFactoryClass.getSuperclass(); - try { - NETWORK_NODE_FACTORY_SETTER = lookup.findSetter(Network.class, "nodeFactory", nodeFactoryClass); - RETE_CONTAINER_CONNECTION_FACTORY_SETTER = lookup.findSetter(ReteContainer.class, "connectionFactory", - connectionFactoryClass); - NODE_PROVISIONER_NODE_FACTORY_SETTER = lookup.findSetter(NodeProvisioner.class, "nodeFactory", - nodeFactoryClass); - NODE_PROVISIONER_CONNECTION_FACTORY_SETTER = lookup.findSetter(NodeProvisioner.class, "connectionFactory", - connectionFactoryClass); - } catch (NoSuchFieldException | IllegalAccessException e) { - throw new IllegalStateException("Cannot get field setter", e); - } - } - - private static Class defineClassFromFile(MethodHandles.Lookup lookup, String name) { - byte[] classBytes; - try (var resource = Network.class.getResourceAsStream(name + ".class")) { - if (resource == null) { - throw new IllegalStateException("Cannot find %s class file".formatted(name)); - } - classBytes = resource.readAllBytes(); - } catch (IOException e) { - throw new IllegalStateException("Cannot read %s class file".formatted(name), e); - } - Class clazz; - try { - clazz = lookup.defineClass(classBytes); - } catch (IllegalAccessException e) { - throw new IllegalStateException("Cannot define %s class".formatted(name), e); - } - return clazz; - } - - public RefineryReteEngine(IQueryBackendContext context, int reteThreads, boolean deleteAndReDeriveEvaluation, - TimelyConfiguration timelyConfiguration) { - super(context, reteThreads, deleteAndReDeriveEvaluation, timelyConfiguration); - installFactories(); - } - - private void installFactories() { - var logger = getLogger(); - Object nodeFactory; - try { - nodeFactory = REFINERY_NODE_FACTORY_CONSTRUCTOR.invoke(logger); - } catch (Error e) { - // Fatal JVM errors should not be wrapped. - throw e; - } catch (Throwable e) { - throw new IllegalStateException("Cannot construct node factory", e); - } - try { - NETWORK_NODE_FACTORY_SETTER.invoke(reteNet, nodeFactory); - } catch (Error e) { - // Fatal JVM errors should not be wrapped. - throw e; - } catch (Throwable e) { - throw new IllegalStateException("Cannot set factory", e); - } - for (var container : reteNet.getContainers()) { - Object connectionFactory; - try { - connectionFactory = REFINERY_CONNECTION_FACTORY_CONSTRUCTOR.invoke(container); - } catch (Error e) { - // Fatal JVM errors should not be wrapped. - throw e; - } catch (Throwable e) { - throw new IllegalStateException("Cannot construct connection factory", e); - } - var provisioner = container.getProvisioner(); - try { - RETE_CONTAINER_CONNECTION_FACTORY_SETTER.invoke(container, connectionFactory); - NODE_PROVISIONER_NODE_FACTORY_SETTER.invoke(provisioner, nodeFactory); - NODE_PROVISIONER_CONNECTION_FACTORY_SETTER.invoke(provisioner, connectionFactory); - } catch (Error e) { - // Fatal JVM errors should not be wrapped. - throw e; - } catch (Throwable e) { - throw new IllegalStateException("Cannot set factory", e); - } - } - } - - @Override - public IQueryBackendFactory getFactory() { - return RefineryReteBackendFactory.INSTANCE; - } -} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/RefineryReteRecipeCompiler.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/RefineryReteRecipeCompiler.java deleted file mode 100644 index fd1b48d8..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/RefineryReteRecipeCompiler.java +++ /dev/null @@ -1,228 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro - * Copyright (c) 2023 The Refinery Authors - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-v20.html. - * SPDX-License-Identifier: EPL-2.0 - *******************************************************************************/ -package tools.refinery.store.query.viatra.internal.rete; - -import org.apache.log4j.Logger; -import org.eclipse.viatra.query.runtime.matchers.backend.IQueryBackendHintProvider; -import org.eclipse.viatra.query.runtime.matchers.context.IQueryCacheContext; -import org.eclipse.viatra.query.runtime.matchers.context.IQueryMetaContext; -import org.eclipse.viatra.query.runtime.matchers.planning.IQueryPlannerStrategy; -import org.eclipse.viatra.query.runtime.matchers.planning.SubPlan; -import org.eclipse.viatra.query.runtime.matchers.planning.operations.PApply; -import org.eclipse.viatra.query.runtime.matchers.planning.operations.PEnumerate; -import org.eclipse.viatra.query.runtime.matchers.psystem.EnumerablePConstraint; -import org.eclipse.viatra.query.runtime.matchers.psystem.analysis.QueryAnalyzer; -import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PQuery; -import org.eclipse.viatra.query.runtime.matchers.psystem.rewriters.PDisjunctionRewriterCacher; -import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; -import org.eclipse.viatra.query.runtime.rete.construction.plancompiler.CompilerHelper; -import org.eclipse.viatra.query.runtime.rete.construction.plancompiler.ReteRecipeCompiler; -import org.eclipse.viatra.query.runtime.rete.matcher.TimelyConfiguration; -import org.eclipse.viatra.query.runtime.rete.recipes.ReteNodeRecipe; -import org.eclipse.viatra.query.runtime.rete.traceability.CompiledSubPlan; -import org.eclipse.viatra.query.runtime.rete.traceability.PlanningTrace; -import org.eclipse.viatra.query.runtime.rete.util.ReteHintOptions; -import org.jetbrains.annotations.Nullable; -import tools.refinery.store.query.viatra.internal.pquery.RepresentativeElectionConstraint; -import tools.refinery.store.query.viatra.internal.rete.recipe.RefineryRecipesFactory; -import tools.refinery.store.query.viatra.internal.pquery.rewriter.RefineryPBodyNormalizer; -import tools.refinery.store.query.viatra.internal.pquery.rewriter.RefinerySurrogateQueryRewriter; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import java.lang.reflect.Field; -import java.util.Map; - -// Since we don't modify VIATRA code, this is our last resort. -@SuppressWarnings("squid:S3011") -public class RefineryReteRecipeCompiler extends ReteRecipeCompiler { - private static final MethodHandle GET_SUB_PLAN_COMPILER_CACHE; - private static final MethodHandle GET_COMPILER_BACK_TRACE; - private static final Field NORMALIZER_FIELD; - private static final MethodHandle DO_COMPILE_DISPATCH; - private static final MethodHandle COMPILE_TO_NATURAL_JOIN; - private static final MethodHandle REFER_QUERY; - - static { - MethodHandles.Lookup lookup; - try { - lookup = MethodHandles.privateLookupIn(ReteRecipeCompiler.class, MethodHandles.lookup()); - } catch (IllegalAccessException e) { - throw new IllegalStateException("Failed to create lookup", e); - } - try { - GET_SUB_PLAN_COMPILER_CACHE = lookup.findGetter(ReteRecipeCompiler.class, "subPlanCompilerCache", - Map.class); - GET_COMPILER_BACK_TRACE = lookup.findGetter(ReteRecipeCompiler.class, "compilerBackTrace", Map.class); - } catch (NoSuchFieldException | IllegalAccessException e) { - throw new IllegalStateException("Failed to find getter", e); - } - - try { - NORMALIZER_FIELD = ReteRecipeCompiler.class.getDeclaredField("normalizer"); - } catch (NoSuchFieldException e) { - throw new IllegalStateException("Failed to find field", e); - } - NORMALIZER_FIELD.setAccessible(true); - - try { - DO_COMPILE_DISPATCH = lookup.findVirtual(ReteRecipeCompiler.class, "doCompileDispatch", - MethodType.methodType(CompiledSubPlan.class, SubPlan.class)); - COMPILE_TO_NATURAL_JOIN = lookup.findVirtual(ReteRecipeCompiler.class, "compileToNaturalJoin", - MethodType.methodType(CompiledSubPlan.class, SubPlan.class, PlanningTrace.class, - PlanningTrace.class)); - REFER_QUERY = lookup.findVirtual(ReteRecipeCompiler.class, "referQuery", - MethodType.methodType(PlanningTrace.class, PQuery.class, SubPlan.class, Tuple.class)); - } catch (NoSuchMethodException | IllegalAccessException e) { - throw new IllegalStateException("Failed to find method", e); - } - } - - private final Map subPlanCompilerCache; - private final Map compilerBackTrace; - - public RefineryReteRecipeCompiler(IQueryPlannerStrategy plannerStrategy, Logger logger, - IQueryMetaContext metaContext, IQueryCacheContext queryCacheContext, - IQueryBackendHintProvider hintProvider, QueryAnalyzer queryAnalyzer, - boolean deleteAndReDeriveEvaluation, TimelyConfiguration timelyEvaluation) { - super(plannerStrategy, logger, metaContext, queryCacheContext, hintProvider, queryAnalyzer, - deleteAndReDeriveEvaluation, timelyEvaluation); - - var normalizer = new PDisjunctionRewriterCacher(new RefinerySurrogateQueryRewriter(), - new RefineryPBodyNormalizer(metaContext) { - - @Override - protected boolean shouldExpandWeakenedAlternatives(PQuery query) { - var hint = hintProvider.getQueryEvaluationHint(query); - return ReteHintOptions.expandWeakenedAlternativeConstraints.getValueOrDefault(hint); - } - - }); - try { - // https://docs.oracle.com/javase/specs/jls/se17/html/jls-17.html#jls-17.5.3 - // "The object should not be made visible to other threads, nor should the final fields be read, - // until all updates to the final fields of the object are complete." - // The {@code super} constructor only sets but doesn't read the {@code normalizer} field, - // therefore this is fine. - NORMALIZER_FIELD.set(this, normalizer); - } catch (IllegalAccessException e) { - throw new IllegalStateException("Failed to set private final field", e); - } - - try { - @SuppressWarnings("unchecked") - var cache = (Map) GET_SUB_PLAN_COMPILER_CACHE.invokeExact( - (ReteRecipeCompiler) this); - subPlanCompilerCache = cache; - @SuppressWarnings("unchecked") - var backTrace = (Map) GET_COMPILER_BACK_TRACE.invokeExact( - (ReteRecipeCompiler) this); - compilerBackTrace = backTrace; - } catch (Error e) { - // Fatal JVM errors should not be wrapped. - throw e; - } catch (Throwable e) { - throw new IllegalStateException("Failed to access private fields", e); - } - } - - @Override - public CompiledSubPlan getCompiledForm(SubPlan plan) { - CompiledSubPlan compiled = subPlanCompilerCache.get(plan); - if (compiled == null) { - compiled = doCompileDispatchExtension(plan); - if (compiled == null) { - compiled = superDoCompileDispatch(plan); - } - subPlanCompilerCache.put(plan, compiled); - compilerBackTrace.put(compiled.getRecipe(), plan); - } - return compiled; - } - - @Nullable - private CompiledSubPlan doCompileDispatchExtension(SubPlan plan) { - var operation = plan.getOperation(); - if (operation instanceof PEnumerate enumerateOperation) { - return doCompileEnumerateExtension(enumerateOperation.getEnumerablePConstraint(), plan); - } else if (operation instanceof PApply applyOperation && - applyOperation.getPConstraint() instanceof EnumerablePConstraint constraint) { - var secondaryParent = doEnumerateDispatchExtension(plan, constraint); - if (secondaryParent != null) { - var primaryParent = getCompiledForm(plan.getParentPlans().get(0)); - return superCompileToNaturalJoin(plan, primaryParent, secondaryParent); - } - } - return null; - } - - @Nullable - private CompiledSubPlan doCompileEnumerateExtension(EnumerablePConstraint constraint, SubPlan plan) { - var coreTrace = doEnumerateDispatchExtension(plan, constraint); - if (coreTrace == null) { - return null; - } - var trimmedTrace = CompilerHelper.checkAndTrimEqualVariables(plan, coreTrace); - return trimmedTrace.cloneFor(plan); - } - - @Nullable - private PlanningTrace doEnumerateDispatchExtension(SubPlan plan, EnumerablePConstraint constraint) { - if (constraint instanceof RepresentativeElectionConstraint representativeElectionConstraint) { - return compileEnumerableExtension(plan, representativeElectionConstraint); - } - return null; - } - - private PlanningTrace compileEnumerableExtension(SubPlan plan, RepresentativeElectionConstraint constraint) { - var referredQuery = constraint.getSupplierKey(); - var callTrace = superReferQuery(referredQuery, plan, constraint.getVariablesTuple()); - var recipe = RefineryRecipesFactory.eINSTANCE.createRepresentativeElectionRecipe(); - recipe.setParent(callTrace.getRecipe()); - recipe.setConnectivity(constraint.getConnectivity()); - return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), recipe, callTrace); - } - - private CompiledSubPlan superDoCompileDispatch(SubPlan plan) { - try { - return (CompiledSubPlan) DO_COMPILE_DISPATCH.invokeExact((ReteRecipeCompiler) this, plan); - } catch (Error | RuntimeException e) { - // Fatal JVM errors and runtime exceptions should not be wrapped. - throw e; - } catch (Throwable e) { - throw new IllegalStateException("Failed to call doCompileDispatch", e); - } - } - - private CompiledSubPlan superCompileToNaturalJoin(SubPlan plan, PlanningTrace leftCompiled, - PlanningTrace rightCompiled) { - try { - return (CompiledSubPlan) COMPILE_TO_NATURAL_JOIN.invokeExact((ReteRecipeCompiler) this, plan, - leftCompiled, rightCompiled); - } catch (Error | RuntimeException e) { - // Fatal JVM errors and runtime exceptions should not be wrapped. - throw e; - } catch (Throwable e) { - throw new IllegalStateException("Failed to call compileToNaturalJoin", e); - } - } - - private PlanningTrace superReferQuery(PQuery query, SubPlan plan, Tuple actualParametersTuple) { - try { - return (PlanningTrace) REFER_QUERY.invokeExact((ReteRecipeCompiler) this, query, plan, - actualParametersTuple); - } catch (Error | RuntimeException e) { - // Fatal JVM errors and runtime exceptions should not be wrapped. - throw e; - } catch (Throwable e) { - throw new IllegalStateException("Failed to call referQuery", e); - } - } -} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/network/RefineryConnectionFactoryExtensions.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/network/RefineryConnectionFactoryExtensions.java deleted file mode 100644 index 0fe5ee27..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/network/RefineryConnectionFactoryExtensions.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.query.viatra.internal.rete.network; - -import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; -import org.eclipse.viatra.query.runtime.rete.network.Node; -import org.eclipse.viatra.query.runtime.rete.network.ReteContainer; -import org.eclipse.viatra.query.runtime.rete.network.Supplier; -import org.eclipse.viatra.query.runtime.rete.remote.Address; -import org.eclipse.viatra.query.runtime.rete.traceability.RecipeTraceInfo; -import tools.refinery.store.query.viatra.internal.rete.recipe.RepresentativeElectionRecipe; - -import java.util.ArrayList; - -public class RefineryConnectionFactoryExtensions { - private final ReteContainer reteContainer; - - public RefineryConnectionFactoryExtensions(ReteContainer reteContainer) { - this.reteContainer = reteContainer; - } - - public boolean connectToParents(RecipeTraceInfo recipeTrace, Node freshNode) { - var recipe = recipeTrace.getRecipe(); - if (recipe instanceof RepresentativeElectionRecipe representativeElectionRecipe) { - connectToParents(representativeElectionRecipe, (RepresentativeElectionNode) freshNode); - return true; - } - return false; - } - - private void connectToParents(RepresentativeElectionRecipe recipe, RepresentativeElectionNode freshNode) { - var parentRecipe = recipe.getParent(); - // Apparently VIATRA ensures that this cast is safe, see - // {@link org.eclipse.viatra.query.runtime.rete.network.ConnectionFactory.connectToParent}. - @SuppressWarnings("unchecked") - var parentAddress = (Address) reteContainer.getNetwork() - .getExistingNodeByRecipe(parentRecipe); - var parentSupplier = reteContainer.getProvisioner().asSupplier(parentAddress); - var tuples = new ArrayList(); - parentSupplier.pullInto(tuples, true); - freshNode.reinitializeWith(tuples); - reteContainer.connect(parentSupplier, freshNode); - } -} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/network/RefineryNodeFactoryExtensions.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/network/RefineryNodeFactoryExtensions.java deleted file mode 100644 index 82b63a55..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/network/RefineryNodeFactoryExtensions.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.query.viatra.internal.rete.network; - -import org.eclipse.viatra.query.runtime.rete.network.ReteContainer; -import org.eclipse.viatra.query.runtime.rete.network.Supplier; -import org.eclipse.viatra.query.runtime.rete.recipes.ReteNodeRecipe; -import org.eclipse.viatra.query.runtime.rete.traceability.TraceInfo; -import org.jetbrains.annotations.Nullable; -import tools.refinery.store.query.viatra.internal.rete.recipe.RepresentativeElectionRecipe; - -public class RefineryNodeFactoryExtensions { - @Nullable - public Supplier createNode(ReteContainer reteContainer, ReteNodeRecipe recipe, TraceInfo... traces) { - var result = instantiateNode(reteContainer, recipe); - if (result == null) { - return null; - } - for (var traceInfo : traces) { - result.assignTraceInfo(traceInfo); - } - return result; - } - - @Nullable - private Supplier instantiateNode(ReteContainer reteContainer, ReteNodeRecipe recipe) { - if (recipe instanceof RepresentativeElectionRecipe representativeElectionRecipe) { - return instantiateRepresentativeElectionNode(reteContainer, representativeElectionRecipe); - } - return null; - } - - private Supplier instantiateRepresentativeElectionNode(ReteContainer reteContainer, - RepresentativeElectionRecipe recipe) { - RepresentativeElectionAlgorithm.Factory algorithmFactory = switch (recipe.getConnectivity()) { - case STRONG -> StronglyConnectedComponentAlgorithm::new; - case WEAK -> WeaklyConnectedComponentAlgorithm::new; - }; - return new RepresentativeElectionNode(reteContainer, algorithmFactory); - } -} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/network/RepresentativeElectionAlgorithm.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/network/RepresentativeElectionAlgorithm.java deleted file mode 100644 index ff5c7158..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/network/RepresentativeElectionAlgorithm.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.query.viatra.internal.rete.network; - -import org.eclipse.viatra.query.runtime.base.itc.graphimpl.Graph; -import org.eclipse.viatra.query.runtime.base.itc.igraph.IGraphObserver; -import org.eclipse.viatra.query.runtime.matchers.util.Direction; - -import java.util.*; - -public abstract class RepresentativeElectionAlgorithm implements IGraphObserver { - protected final Graph graph; - protected final Map representatives = new HashMap<>(); - protected final Map> components = new HashMap<>(); - private RepresentativeElectionNode observer; - - protected RepresentativeElectionAlgorithm(Graph graph) { - this.graph = graph; - initializeComponents(); - graph.attachObserver(this); - } - - protected abstract void initializeComponents(); - - protected void initializeSet(Set set) { - var iterator = set.iterator(); - if (!iterator.hasNext()) { - // Set is empty. - return; - } - var representative = iterator.next(); - for (var node : set) { - var oldRepresentative = representatives.put(node, representative); - if (oldRepresentative != null && !representative.equals(oldRepresentative)) { - throw new IllegalStateException("Node %s is already in a set represented by %s, cannot add it to %s" - .formatted(node, oldRepresentative, set)); - } - } - components.put(representative, set); - } - - protected void merge(Object leftRepresentative, Object rightRepresentative) { - if (leftRepresentative.equals(rightRepresentative)) { - return; - } - var leftSet = getComponent(leftRepresentative); - var rightSet = getComponent(rightRepresentative); - if (leftSet.size() < rightSet.size()) { - merge(rightRepresentative, rightSet, leftRepresentative, leftSet); - } else { - merge(leftRepresentative, leftSet, rightRepresentative, rightSet); - } - } - - private void merge(Object preservedRepresentative, Set preservedSet, Object removedRepresentative, - Set removedSet) { - components.remove(removedRepresentative); - for (var node : removedSet) { - representatives.put(node, preservedRepresentative); - preservedSet.add(node); - notifyToObservers(node, removedRepresentative, preservedRepresentative); - } - } - - protected void assignNewRepresentative(Object oldRepresentative, Set set) { - var iterator = set.iterator(); - if (!iterator.hasNext()) { - return; - } - var newRepresentative = iterator.next(); - components.put(newRepresentative, set); - for (var node : set) { - var oldRepresentativeOfNode = representatives.put(node, newRepresentative); - if (!oldRepresentative.equals(oldRepresentativeOfNode)) { - throw new IllegalArgumentException("Node %s was not represented by %s but by %s" - .formatted(node, oldRepresentative, oldRepresentativeOfNode)); - } - notifyToObservers(node, oldRepresentative, newRepresentative); - } - } - - public void setObserver(RepresentativeElectionNode observer) { - this.observer = observer; - } - - public Map> getComponents() { - return components; - } - - public Object getRepresentative(Object node) { - return representatives.get(node); - } - - public Set getComponent(Object representative) { - return components.get(representative); - } - - public void dispose() { - graph.detachObserver(this); - } - - @Override - public void nodeInserted(Object n) { - var component = new HashSet<>(1); - component.add(n); - initializeSet(component); - notifyToObservers(n, n, Direction.INSERT); - } - - @Override - public void nodeDeleted(Object n) { - var representative = representatives.remove(n); - if (!representative.equals(n)) { - throw new IllegalStateException("Trying to delete node with dangling edges"); - } - components.remove(representative); - notifyToObservers(n, representative, Direction.DELETE); - } - - protected void notifyToObservers(Object node, Object oldRepresentative, Object newRepresentative) { - notifyToObservers(node, oldRepresentative, Direction.DELETE); - notifyToObservers(node, newRepresentative, Direction.INSERT); - } - - protected void notifyToObservers(Object node, Object representative, Direction direction) { - if (observer != null) { - observer.tupleChanged(node, representative, direction); - } - } - - interface Factory { - RepresentativeElectionAlgorithm create(Graph graph); - } -} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/network/RepresentativeElectionNode.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/network/RepresentativeElectionNode.java deleted file mode 100644 index 701f6ffe..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/network/RepresentativeElectionNode.java +++ /dev/null @@ -1,120 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2010-2012, Tamas Szabo, Gabor Bergmann, Istvan Rath and Daniel Varro - * Copyright (c) 2023 The Refinery Authors - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-v20.html. - * SPDX-License-Identifier: EPL-2.0 - *******************************************************************************/ -package tools.refinery.store.query.viatra.internal.rete.network; - -import org.eclipse.viatra.query.runtime.base.itc.graphimpl.Graph; -import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; -import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; -import org.eclipse.viatra.query.runtime.matchers.util.Clearable; -import org.eclipse.viatra.query.runtime.matchers.util.Direction; -import org.eclipse.viatra.query.runtime.matchers.util.timeline.Timeline; -import org.eclipse.viatra.query.runtime.rete.network.ReteContainer; -import org.eclipse.viatra.query.runtime.rete.network.communication.Timestamp; -import org.eclipse.viatra.query.runtime.rete.single.SingleInputNode; - -import java.util.Collection; -import java.util.Map; - -public class RepresentativeElectionNode extends SingleInputNode implements Clearable { - private final RepresentativeElectionAlgorithm.Factory algorithmFactory; - private Graph graph; - private RepresentativeElectionAlgorithm algorithm; - - public RepresentativeElectionNode(ReteContainer reteContainer, - RepresentativeElectionAlgorithm.Factory algorithmFactory) { - super(reteContainer); - this.algorithmFactory = algorithmFactory; - graph = new Graph<>(); - algorithm = algorithmFactory.create(graph); - algorithm.setObserver(this); - reteContainer.registerClearable(this); - } - - @Override - public void networkStructureChanged() { - if (reteContainer.isTimelyEvaluation() && reteContainer.getCommunicationTracker().isInRecursiveGroup(this)) { - throw new IllegalStateException(this + " cannot be used in recursive differential dataflow evaluation!"); - } - super.networkStructureChanged(); - } - - public void reinitializeWith(Collection tuples) { - algorithm.dispose(); - graph = new Graph<>(); - for (var tuple : tuples) { - insertEdge(tuple.get(0), tuple.get(1)); - } - algorithm = algorithmFactory.create(graph); - algorithm.setObserver(this); - } - - public void tupleChanged(Object source, Object representative, Direction direction) { - var tuple = Tuples.staticArityFlatTupleOf(source, representative); - propagateUpdate(direction, tuple, Timestamp.ZERO); - } - - @Override - public void clear() { - algorithm.dispose(); - graph = new Graph<>(); - algorithm = algorithmFactory.create(graph); - } - - @Override - public void update(Direction direction, Tuple updateElement, Timestamp timestamp) { - var source = updateElement.get(0); - var target = updateElement.get(1); - switch (direction) { - case INSERT -> insertEdge(source, target); - case DELETE -> deleteEdge(source, target); - default -> throw new IllegalArgumentException("Unknown direction: " + direction); - } - } - - private void insertEdge(Object source, Object target) { - graph.insertNode(source); - graph.insertNode(target); - graph.insertEdge(source, target); - } - - private void deleteEdge(Object source, Object target) { - graph.deleteEdgeIfExists(source, target); - if (isIsolated(source)) { - graph.deleteNode(source); - } - if (!source.equals(target) && isIsolated(target)) { - graph.deleteNode(target); - } - } - - private boolean isIsolated(Object node) { - return graph.getTargetNodes(node).isEmpty() && graph.getSourceNodes(node).isEmpty(); - } - - @Override - public void pullInto(Collection collector, boolean flush) { - for (var entry : algorithm.getComponents().entrySet()) { - var representative = entry.getKey(); - for (var node : entry.getValue()) { - collector.add(Tuples.staticArityFlatTupleOf(node, representative)); - } - } - } - - @Override - public void pullIntoWithTimeline(Map> collector, boolean flush) { - // Use all zero timestamps because this node cannot be used in recursive groups anyway. - for (var entry : algorithm.getComponents().entrySet()) { - var representative = entry.getKey(); - for (var node : entry.getValue()) { - collector.put(Tuples.staticArityFlatTupleOf(node, representative), Timestamp.INSERT_AT_ZERO_TIMELINE); - } - } - } -} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/network/StronglyConnectedComponentAlgorithm.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/network/StronglyConnectedComponentAlgorithm.java deleted file mode 100644 index 11155f3e..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/network/StronglyConnectedComponentAlgorithm.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.query.viatra.internal.rete.network; - -import org.eclipse.viatra.query.runtime.base.itc.alg.misc.GraphHelper; -import org.eclipse.viatra.query.runtime.base.itc.alg.misc.bfs.BFS; -import org.eclipse.viatra.query.runtime.base.itc.alg.misc.scc.SCC; -import org.eclipse.viatra.query.runtime.base.itc.graphimpl.Graph; - -import java.util.Collection; -import java.util.Set; - -public class StronglyConnectedComponentAlgorithm extends RepresentativeElectionAlgorithm { - public StronglyConnectedComponentAlgorithm(Graph graph) { - super(graph); - } - - @Override - protected void initializeComponents() { - var computedSCCs = SCC.computeSCC(graph).getSccs(); - for (var computedSCC : computedSCCs) { - initializeSet(computedSCC); - } - } - - @Override - public void edgeInserted(Object source, Object target) { - var sourceRoot = getRepresentative(source); - var targetRoot = getRepresentative(target); - if (sourceRoot.equals(targetRoot)) { - // New edge does not change strongly connected components. - return; - } - if (BFS.isReachable(target, source, graph)) { - merge(sourceRoot, targetRoot); - } - } - - @Override - public void edgeDeleted(Object source, Object target) { - var sourceRoot = getRepresentative(source); - var targetRoot = getRepresentative(target); - if (!sourceRoot.equals(targetRoot)) { - // New edge does not change strongly connected components. - return; - } - var component = GraphHelper.getSubGraph(getComponent(sourceRoot), graph); - if (!BFS.isReachable(source, target, component)) { - var newSCCs = SCC.computeSCC(component).getSccs(); - split(sourceRoot, newSCCs); - } - } - - private void split(Object preservedRepresentative, Collection> sets) { - for (var set : sets) { - if (set.contains(preservedRepresentative)) { - components.put(preservedRepresentative, set); - } else { - assignNewRepresentative(preservedRepresentative, set); - } - } - } -} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/network/WeaklyConnectedComponentAlgorithm.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/network/WeaklyConnectedComponentAlgorithm.java deleted file mode 100644 index 118004dd..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/network/WeaklyConnectedComponentAlgorithm.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.query.viatra.internal.rete.network; - -import org.eclipse.viatra.query.runtime.base.itc.graphimpl.Graph; - -import java.util.ArrayDeque; -import java.util.HashSet; -import java.util.Set; - -public class WeaklyConnectedComponentAlgorithm extends RepresentativeElectionAlgorithm { - public WeaklyConnectedComponentAlgorithm(Graph graph) { - super(graph); - } - - @Override - protected void initializeComponents() { - for (var node : graph.getAllNodes()) { - if (representatives.containsKey(node)) { - continue; - } - var reachable = getReachableNodes(node); - initializeSet(reachable); - } - } - - @Override - public void edgeInserted(Object source, Object target) { - var sourceRoot = getRepresentative(source); - var targetRoot = getRepresentative(target); - merge(sourceRoot, targetRoot); - } - - @Override - public void edgeDeleted(Object source, Object target) { - var sourceRoot = getRepresentative(source); - var targetRoot = getRepresentative(target); - if (!sourceRoot.equals(targetRoot)) { - throw new IllegalArgumentException("Trying to remove edge not in graph"); - } - var targetReachable = getReachableNodes(target); - if (!targetReachable.contains(source)) { - split(sourceRoot, targetReachable); - } - } - - private void split(Object sourceRepresentative, Set targetReachable) { - var sourceComponent = getComponent(sourceRepresentative); - sourceComponent.removeAll(targetReachable); - if (targetReachable.contains(sourceRepresentative)) { - components.put(sourceRepresentative, targetReachable); - assignNewRepresentative(sourceRepresentative, sourceComponent); - } else { - assignNewRepresentative(sourceRepresentative, targetReachable); - } - } - - private Set getReachableNodes(Object source) { - var retSet = new HashSet<>(); - retSet.add(source); - var nodeQueue = new ArrayDeque<>(); - nodeQueue.addLast(source); - - while (!nodeQueue.isEmpty()) { - var node = nodeQueue.removeFirst(); - for (var neighbor : graph.getTargetNodes(node).distinctValues()) { - if (!retSet.contains(neighbor)) { - retSet.add(neighbor); - nodeQueue.addLast(neighbor); - } - } - for (var neighbor : graph.getSourceNodes(node).distinctValues()) { - if (!retSet.contains(neighbor)) { - retSet.add(neighbor); - nodeQueue.addLast(neighbor); - } - } - } - - return retSet; - } -} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/recipe/RefineryRecipesFactory.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/recipe/RefineryRecipesFactory.java deleted file mode 100644 index 1f8b3034..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/recipe/RefineryRecipesFactory.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.query.viatra.internal.rete.recipe; - -import org.eclipse.emf.ecore.EDataType; -import org.eclipse.emf.ecore.EFactory; -import tools.refinery.store.query.literal.Connectivity; - -// Naming and index computation follows EMF conventions. -@SuppressWarnings("squid:S115") -public interface RefineryRecipesFactory extends EFactory { - RefineryRecipesFactory eINSTANCE = RefineryRecipesFactoryImpl.init(); - - RepresentativeElectionRecipe createRepresentativeElectionRecipe(); - - Connectivity createConnectivityFromString(EDataType eDataType, String initialValue); - - String convertConnectivityToString(EDataType eDataType, Object instanceValue); -} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/recipe/RefineryRecipesFactoryImpl.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/recipe/RefineryRecipesFactoryImpl.java deleted file mode 100644 index 4e2a695c..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/recipe/RefineryRecipesFactoryImpl.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.query.viatra.internal.rete.recipe; - -import org.eclipse.emf.ecore.EClass; -import org.eclipse.emf.ecore.EDataType; -import org.eclipse.emf.ecore.EObject; -import org.eclipse.emf.ecore.EPackage; -import org.eclipse.emf.ecore.impl.EFactoryImpl; -import org.eclipse.emf.ecore.plugin.EcorePlugin; -import tools.refinery.store.query.literal.Connectivity; - -public class RefineryRecipesFactoryImpl extends EFactoryImpl implements RefineryRecipesFactory { - public static RefineryRecipesFactory init() { - try { - var factory = (RefineryRecipesFactory) EPackage.Registry.INSTANCE.getEFactory( - RefineryRecipesPackage.eNS_URI); - if (factory != null) { - return factory; - } - } - catch (Exception exception) { - EcorePlugin.INSTANCE.log(exception); - } - return new RefineryRecipesFactoryImpl(); - } - - @Override - public EObject create(EClass eClass) { - if (eClass.getClassifierID() == RefineryRecipesPackage.REPRESENTATIVE_ELECTION_RECIPE) { - return createRepresentativeElectionRecipe(); - } else { - throw new IllegalArgumentException("The class '%s' is not a valid classifier".formatted(eClass.getName())); - } - } - - @Override - public Object createFromString(EDataType eDataType, String stringValue) { - if (eDataType.getClassifierID() == RefineryRecipesPackage.CONNECTIVITY) { - return createConnectivityFromString(eDataType, stringValue); - } else { - throw new IllegalArgumentException("The datatype '%s' is not a valid classifier" - .formatted(eDataType.getName())); - } - } - - @Override - public String convertToString(EDataType eDataType, Object objectValue) { - if (eDataType.getClassifierID() == RefineryRecipesPackage.CONNECTIVITY) { - return convertConnectivityToString(eDataType, objectValue); - } else { - throw new IllegalArgumentException("The datatype '%s' is not a valid classifier" - .formatted(eDataType.getName())); - } - } - - @Override - public RepresentativeElectionRecipe createRepresentativeElectionRecipe() { - return new RepresentativeElectionRecipeImpl(); - } - - @Override - public Connectivity createConnectivityFromString(EDataType eDataType, String initialValue) { - return (Connectivity) super.createFromString(eDataType, initialValue); - } - - @Override - public String convertConnectivityToString(EDataType eDataType, Object instanceValue) { - return super.convertToString(eDataType, instanceValue); - } -} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/recipe/RefineryRecipesPackage.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/recipe/RefineryRecipesPackage.java deleted file mode 100644 index 6c933c45..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/recipe/RefineryRecipesPackage.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.query.viatra.internal.rete.recipe; - -import org.eclipse.emf.ecore.*; -import org.eclipse.viatra.query.runtime.rete.recipes.RecipesPackage; - -// Naming and index computation follows EMF conventions. -@SuppressWarnings({"squid:S100", "squid:S115", "PointlessArithmeticExpression"}) -public interface RefineryRecipesPackage extends EPackage { - String eNAME = "refineryReteRecipes"; - - String eNS_URI = "https://refinery.tools/emf/2021/RefineryReteRecipes"; - - String eNS_PREFIX = "refineryReteRecipes"; - - RefineryRecipesPackage eINSTANCE = RefineryRecipesPackageImpl.init(); - - int REPRESENTATIVE_ELECTION_RECIPE = 0; - - int REPRESENTATIVE_ELECTION_RECIPE__CONNECTIVITY = RecipesPackage.ALPHA_RECIPE_FEATURE_COUNT + 0; - - int REPRESENTATIVE_ELECTION_RECIPE_FEATURE_COUNT = RecipesPackage.ALPHA_RECIPE_FEATURE_COUNT + 1; - - int REPRESENTATIVE_ELECTION_RECIPE__GET_ARITY = RecipesPackage.ALPHA_RECIPE_OPERATION_COUNT + 0; - - int REPRESENTATIVE_ELECTION_RECIPE_OPERATION_COUNT = RecipesPackage.ALPHA_RECIPE_OPERATION_COUNT + 1; - - int CONNECTIVITY = 1; - - EClass getRepresentativeElectionRecipe(); - - EAttribute getRepresentativeElectionRecipe_Connectivity(); - - EOperation getRepresentativeElectionRecipe_GetArity(); - - EDataType getConnectivity(); -} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/recipe/RefineryRecipesPackageImpl.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/recipe/RefineryRecipesPackageImpl.java deleted file mode 100644 index d5073402..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/recipe/RefineryRecipesPackageImpl.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.query.viatra.internal.rete.recipe; - -import org.eclipse.emf.ecore.*; -import org.eclipse.emf.ecore.impl.EPackageImpl; -import org.eclipse.viatra.query.runtime.rete.recipes.RecipesPackage; -import tools.refinery.store.query.literal.Connectivity; - -public class RefineryRecipesPackageImpl extends EPackageImpl implements RefineryRecipesPackage { - private static boolean isInstanceInitialized; - private boolean isCreated; - private boolean isInitialized; - private EClass representativeElectionRecipe; - private EDataType connectivity; - - public static RefineryRecipesPackage init() { - if (isInstanceInitialized) { - return (RefineryRecipesPackage) Registry.INSTANCE.getEPackage(eNS_URI); - } - var thePackage = Registry.INSTANCE.get(eNS_URI) instanceof RefineryRecipesPackageImpl impl ? impl : - new RefineryRecipesPackageImpl(); - isInstanceInitialized = true; - thePackage.createPackageContents(); - thePackage.initializePackageContents(); - thePackage.freeze(); - Registry.INSTANCE.put(eNS_URI, thePackage); - return thePackage; - } - - private RefineryRecipesPackageImpl() { - super(eNS_URI, RefineryRecipesFactory.eINSTANCE); - } - - @Override - public EClass getRepresentativeElectionRecipe() { - return representativeElectionRecipe; - } - - @Override - public EAttribute getRepresentativeElectionRecipe_Connectivity() { - return (EAttribute) representativeElectionRecipe.getEStructuralFeatures().get(0); - } - - @Override - public EOperation getRepresentativeElectionRecipe_GetArity() { - return representativeElectionRecipe.getEOperations().get(0); - } - - @Override - public EDataType getConnectivity() { - return connectivity; - } - - public void createPackageContents() { - if (isCreated) { - return; - } - isCreated = true; - - representativeElectionRecipe = createEClass(REPRESENTATIVE_ELECTION_RECIPE); - createEAttribute(representativeElectionRecipe, REPRESENTATIVE_ELECTION_RECIPE__CONNECTIVITY); - createEOperation(representativeElectionRecipe, REPRESENTATIVE_ELECTION_RECIPE__GET_ARITY); - - connectivity = createEDataType(CONNECTIVITY); - } - - public void initializePackageContents() { - if (isInitialized) { - return; - } - isInitialized = true; - - setName(eNAME); - setNsPrefix(eNS_PREFIX); - setNsURI(eNS_URI); - - representativeElectionRecipe.getESuperTypes().add(RecipesPackage.Literals.ALPHA_RECIPE); - - initEClass(representativeElectionRecipe, RepresentativeElectionRecipe.class, - "RepresentativeElectionRecipe", !IS_ABSTRACT, !IS_INTERFACE, IS_GENERATED_INSTANCE_CLASS); - initEAttribute(getRepresentativeElectionRecipe_Connectivity(), getConnectivity(), "connectivity", null, 0, 1, - RepresentativeElectionRecipe.class, !IS_TRANSIENT, !IS_VOLATILE, IS_CHANGEABLE, !IS_UNSETTABLE, !IS_ID, - IS_UNIQUE, !IS_DERIVED, IS_ORDERED); - initEOperation(getRepresentativeElectionRecipe_GetArity(), EcorePackage.Literals.EINT, "getArity", 0, 1, - !IS_UNIQUE, IS_ORDERED); - - initEDataType(connectivity, Connectivity.class, "Connectivity", IS_SERIALIZABLE, - !IS_GENERATED_INSTANCE_CLASS); - - createResource(eNS_URI); - } -} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/recipe/RepresentativeElectionRecipe.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/recipe/RepresentativeElectionRecipe.java deleted file mode 100644 index def825c2..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/recipe/RepresentativeElectionRecipe.java +++ /dev/null @@ -1,17 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.query.viatra.internal.rete.recipe; - -import org.eclipse.viatra.query.runtime.rete.recipes.AlphaRecipe; -import tools.refinery.store.query.literal.Connectivity; - -public interface RepresentativeElectionRecipe extends AlphaRecipe { - Connectivity getConnectivity(); - - void setConnectivity(Connectivity connectivity); - - int getArity(); -} diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/recipe/RepresentativeElectionRecipeImpl.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/recipe/RepresentativeElectionRecipeImpl.java deleted file mode 100644 index 959836d2..00000000 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/rete/recipe/RepresentativeElectionRecipeImpl.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 The Refinery Authors - * - * SPDX-License-Identifier: EPL-2.0 - */ -package tools.refinery.store.query.viatra.internal.rete.recipe; - -import org.eclipse.emf.common.notify.Notification; -import org.eclipse.emf.common.util.EList; -import org.eclipse.emf.ecore.EClass; -import org.eclipse.emf.ecore.impl.ENotificationImpl; -import org.eclipse.viatra.query.runtime.rete.recipes.RecipesPackage; -import org.eclipse.viatra.query.runtime.rete.recipes.ReteNodeRecipe; -import org.eclipse.viatra.query.runtime.rete.recipes.impl.AlphaRecipeImpl; -import tools.refinery.store.query.literal.Connectivity; - -import java.lang.reflect.InvocationTargetException; - -public class RepresentativeElectionRecipeImpl extends AlphaRecipeImpl implements RepresentativeElectionRecipe { - private Connectivity connectivity; - - @Override - protected EClass eStaticClass() { - return RefineryRecipesPackage.eINSTANCE.getRepresentativeElectionRecipe(); - } - - @Override - public Connectivity getConnectivity() { - return connectivity; - } - - @Override - public void setConnectivity(Connectivity newStrong) { - var oldConnectivity = connectivity; - connectivity = newStrong; - if (eNotificationRequired()) { - eNotify(new ENotificationImpl(this, Notification.SET, - RefineryRecipesPackage.REPRESENTATIVE_ELECTION_RECIPE__CONNECTIVITY, oldConnectivity, - connectivity)); - } - } - - @Override - public int getArity() { - return 2; - } - - @Override - public Object eGet(int featureID, boolean resolve, boolean coreType) { - if (featureID == RefineryRecipesPackage.REPRESENTATIVE_ELECTION_RECIPE__CONNECTIVITY) { - return getConnectivity(); - } - return super.eGet(featureID, resolve, coreType); - } - - @Override - public void eSet(int featureID, Object newValue) { - if (featureID == RefineryRecipesPackage.REPRESENTATIVE_ELECTION_RECIPE__CONNECTIVITY) { - setConnectivity((Connectivity) newValue); - } else { - super.eSet(featureID, newValue); - } - } - - @Override - public void eUnset(int featureID) { - if (featureID == RefineryRecipesPackage.REPRESENTATIVE_ELECTION_RECIPE__CONNECTIVITY) { - setConnectivity(null); - } else { - super.eUnset(featureID); - } - } - - @Override - public boolean eIsSet(int featureID) { - if (featureID == RefineryRecipesPackage.REPRESENTATIVE_ELECTION_RECIPE__CONNECTIVITY) { - return connectivity != null; - } - return super.eIsSet(featureID); - } - - @Override - public int eDerivedOperationID(int baseOperationID, Class baseClass) { - if (baseClass == ReteNodeRecipe.class && baseOperationID == RecipesPackage.RETE_NODE_RECIPE___GET_ARITY) { - return RefineryRecipesPackage.REPRESENTATIVE_ELECTION_RECIPE__GET_ARITY; - } - return super.eDerivedOperationID(baseOperationID, baseClass); - } - - @Override - public Object eInvoke(int operationID, EList arguments) throws InvocationTargetException { - if (operationID == RefineryRecipesPackage.REPRESENTATIVE_ELECTION_RECIPE__GET_ARITY) { - return getArity(); - } - return super.eInvoke(operationID, arguments); - } - - @Override - public String toString() { - if (eIsProxy()) { - return super.toString(); - } - return "%s (connectivity: %s)".formatted(super.toString(), connectivity); - } -} 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 986bb0b1..e1bc9efc 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 @@ -5,9 +5,9 @@ */ package tools.refinery.store.query.viatra.internal.update; -import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; -import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContextListener; -import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContextListener; +import tools.refinery.viatra.runtime.matchers.tuple.ITuple; import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; import tools.refinery.store.query.view.AnySymbolView; import tools.refinery.store.query.view.SymbolView; 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 efdbfcbe..73c4a3f9 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 @@ -5,10 +5,10 @@ */ package tools.refinery.store.query.viatra.internal.update; -import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; -import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContextListener; -import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; -import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContextListener; +import tools.refinery.viatra.runtime.matchers.tuple.ITuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; import java.util.Arrays; import java.util.Objects; 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 index f1a2ac7c..d0cdda72 100644 --- 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 @@ -5,10 +5,10 @@ */ package tools.refinery.store.query.viatra.internal.update; -import org.eclipse.viatra.query.runtime.matchers.context.IInputKey; -import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContextListener; -import org.eclipse.viatra.query.runtime.matchers.tuple.ITuple; -import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContextListener; +import tools.refinery.viatra.runtime.matchers.tuple.ITuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; import tools.refinery.store.model.Interpretation; import tools.refinery.store.model.InterpretationListener; import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; 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 index 45d35571..9dc739f1 100644 --- 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 @@ -5,7 +5,7 @@ */ package tools.refinery.store.query.viatra.internal.update; -import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; import tools.refinery.store.model.Interpretation; import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; import tools.refinery.store.query.view.SymbolView; diff --git a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TuplePreservingViewUpdateListener.java b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TuplePreservingViewUpdateListener.java index c18dbafb..7dbd50b3 100644 --- a/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TuplePreservingViewUpdateListener.java +++ b/subprojects/store-query-viatra/src/main/java/tools/refinery/store/query/viatra/internal/update/TuplePreservingViewUpdateListener.java @@ -5,7 +5,7 @@ */ package tools.refinery.store.query.viatra.internal.update; -import org.eclipse.viatra.query.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; import tools.refinery.store.model.Interpretation; import tools.refinery.store.query.viatra.internal.ViatraModelQueryAdapterImpl; import tools.refinery.store.query.view.TuplePreservingView; 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 index 3d2d5f83..85bdc204 100644 --- 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 @@ -5,7 +5,7 @@ */ package tools.refinery.store.query.viatra; -import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; import tools.refinery.store.model.ModelStore; import tools.refinery.store.query.ModelQueryAdapter; import tools.refinery.store.query.dnf.Dnf; 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 index 7f5c24fe..7190d8f1 100644 --- 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 @@ -5,7 +5,7 @@ */ package tools.refinery.store.query.viatra; -import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; import tools.refinery.store.map.Cursor; import tools.refinery.store.model.ModelStore; import tools.refinery.store.query.ModelQueryAdapter; 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 8e945731..ce403e3a 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 @@ -5,7 +5,7 @@ */ package tools.refinery.store.query.viatra; -import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; import org.junit.jupiter.api.Test; import tools.refinery.store.model.ModelStore; import tools.refinery.store.query.ModelQueryAdapter; 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 66f043c6..5a484119 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 @@ -5,7 +5,7 @@ */ package tools.refinery.store.query.viatra; -import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import tools.refinery.store.model.ModelStore; 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 index 968c6c5e..319797a0 100644 --- 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 @@ -5,7 +5,7 @@ */ package tools.refinery.store.query.viatra.internal.matcher; -import org.eclipse.viatra.query.runtime.matchers.tuple.*; +import tools.refinery.viatra.runtime.matchers.tuple.*; import org.junit.jupiter.api.Test; import tools.refinery.store.tuple.Tuple; import tools.refinery.store.tuple.*; 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 index dc0e92c8..5f88e04b 100644 --- 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 @@ -5,7 +5,7 @@ */ package tools.refinery.store.query.viatra.tests; -import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; /** * Overrides {@link QueryEvaluationHint#toString()} for pretty names in parametric test names. 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 index 9e75d5f3..ed356eeb 100644 --- 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 @@ -5,7 +5,7 @@ */ package tools.refinery.store.query.viatra.tests; -import org.eclipse.viatra.query.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.ArgumentsProvider; diff --git a/subprojects/viatra-runtime-base-itc/about.html b/subprojects/viatra-runtime-base-itc/about.html new file mode 100644 index 00000000..d1d5593a --- /dev/null +++ b/subprojects/viatra-runtime-base-itc/about.html @@ -0,0 +1,26 @@ + + + + +About + + + +

About This Content

+ +

March 18, 2019

+

License

+ +

The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise indicated below, the Content is provided to you under the terms and conditions of the +Eclipse Public License Version 2.0 ("EPL"). A copy of the EPL is available at http://www.eclipse.org/legal/epl-v20.html. +For purposes of the EPL, "Program" will mean the Content.

+ +

If you did not receive this Content directly from the Eclipse Foundation, the Content is being redistributed by another party ("Redistributor") and different terms and conditions may +apply to your use of any object code in the Content. Check the Redistributor's license that was provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise +indicated below, the terms and conditions of the EPL still apply to any source code in the Content and such source code may be obtained at http://www.eclipse.org.

+ + diff --git a/subprojects/viatra-runtime-base-itc/build.gradle.kts b/subprojects/viatra-runtime-base-itc/build.gradle.kts new file mode 100644 index 00000000..2fcdf5c4 --- /dev/null +++ b/subprojects/viatra-runtime-base-itc/build.gradle.kts @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ + +plugins { + id("tools.refinery.gradle.java-library") +} + +dependencies { + implementation(project(":refinery-viatra-runtime-matchers")) +} diff --git a/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/counting/CountingAlg.java b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/counting/CountingAlg.java new file mode 100644 index 00000000..d0367cde --- /dev/null +++ b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/counting/CountingAlg.java @@ -0,0 +1,226 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.base.itc.alg.counting; + +import java.util.List; +import java.util.Set; + +import tools.refinery.viatra.runtime.base.itc.alg.misc.DFSPathFinder; +import tools.refinery.viatra.runtime.base.itc.alg.misc.IGraphPathFinder; +import tools.refinery.viatra.runtime.base.itc.alg.misc.ITcRelation; +import tools.refinery.viatra.runtime.base.itc.igraph.IBiDirectionalGraphDataSource; +import tools.refinery.viatra.runtime.base.itc.igraph.IBiDirectionalWrapper; +import tools.refinery.viatra.runtime.base.itc.igraph.IGraphDataSource; +import tools.refinery.viatra.runtime.base.itc.igraph.IGraphObserver; +import tools.refinery.viatra.runtime.base.itc.igraph.ITcDataSource; +import tools.refinery.viatra.runtime.base.itc.igraph.ITcObserver; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.IMemoryView; + +/** + * This class is the optimized implementation of the Counting algorithm. + * + * @author Tamas Szabo + * + * @param + * the type parameter of the nodes in the graph data source + */ +public class CountingAlg implements IGraphObserver, ITcDataSource { + + private CountingTcRelation tc = null; + private IBiDirectionalGraphDataSource gds = null; + private List> observers; + + /** + * Constructs a new Counting algorithm and initializes the transitive closure relation with the given graph data + * source. Attach itself on the graph data source as an observer. + * + * @param gds + * the graph data source instance + */ + public CountingAlg(IGraphDataSource gds) { + + if (gds instanceof IBiDirectionalGraphDataSource) { + this.gds = (IBiDirectionalGraphDataSource) gds; + } else { + this.gds = new IBiDirectionalWrapper(gds); + } + + observers = CollectionsFactory.>createObserverList(); + tc = new CountingTcRelation(true); + + initTc(); + gds.attachObserver(this); + } + + /** + * Initializes the transitive closure relation. + */ + private void initTc() { + this.setTcRelation(CountingTcRelation.createFrom(gds)); + } + + @Override + public void edgeInserted(V source, V target) { + if (!source.equals(target)) { + deriveTc(source, target, true); + } + } + + @Override + public void edgeDeleted(V source, V target) { + if (!source.equals(target)) { + deriveTc(source, target, false); + } + } + + @Override + public void nodeInserted(V n) { + + } + + @Override + public void nodeDeleted(V n) { + this.tc.deleteTupleEnd(n); + } + + /** + * Derives the transitive closure relation when an edge is inserted or deleted. + * + * @param source + * the source of the edge + * @param target + * the target of the edge + * @param dCount + * the value is -1 if an edge was deleted and +1 if an edge was inserted + */ + private void deriveTc(V source, V target, boolean isInsertion) { + + // if (dCount == 1 && isReachable(target, source)) { + // System.out.println("The graph contains cycle with (" + source + ","+ target + ") edge!"); + // } + + CountingTcRelation dtc = new CountingTcRelation(false); + Set tupEnds = null; + + // 1. d(tc(x,y)) :- d(l(x,y)) + if (tc.updateTuple(source, target, isInsertion)) { + dtc.updateTuple(source, target, true /* deltas implicitly have the same sign as isInsertion*/); + notifyTcObservers(source, target, isInsertion); + } + + // 2. d(tc(x,y)) :- d(l(x,z)) & tc(z,y) + tupEnds = tc.getTupleEnds(target); + if (tupEnds != null) { + for (V tupEnd : tupEnds) { + if (!tupEnd.equals(source)) { + if (tc.updateTuple(source, tupEnd, isInsertion)) { + dtc.updateTuple(source, tupEnd, true /* deltas implicitly have the same sign as isInsertion*/); + notifyTcObservers(source, tupEnd, isInsertion); + } + } + } + } + + // 3. d(tc(x,y)) :- lv(x,z) & d(tc(z,y)) + CountingTcRelation newTuples = dtc; + CountingTcRelation tmp = null; + dtc = new CountingTcRelation(false); + + IMemoryView nodes = null; + + while (!newTuples.isEmpty()) { + + tmp = dtc; + dtc = newTuples; + newTuples = tmp; + newTuples.clear(); + + for (V tS : dtc.getTupleStarts()) { + nodes = gds.getSourceNodes(tS); + for (V nS : nodes.distinctValues()) { + int count = nodes.getCount(nS); + for (int i = 0; i < count; i++) { + tupEnds = dtc.getTupleEnds(tS); + if (tupEnds != null) { + for (V tT : tupEnds) { + if (!nS.equals(tT)) { + if (tc.updateTuple(nS, tT, isInsertion)) { + newTuples.updateTuple(nS, tT, true /* deltas implicitly have the same sign as isInsertion*/); + notifyTcObservers(nS, tT, isInsertion); + } + } + } + } + } + } + } + } + + // System.out.println(tc); + } + + public ITcRelation getTcRelation() { + return this.tc; + } + + public void setTcRelation(CountingTcRelation tc) { + this.tc = tc; + } + + @Override + public boolean isReachable(V source, V target) { + return tc.containsTuple(source, target); + } + + @Override + public void attachObserver(ITcObserver to) { + this.observers.add(to); + + } + + @Override + public void detachObserver(ITcObserver to) { + this.observers.remove(to); + } + + @Override + public Set getAllReachableTargets(V source) { + return tc.getTupleEnds(source); + } + + @Override + public Set getAllReachableSources(V target) { + return tc.getTupleStarts(target); + } + + private void notifyTcObservers(V source, V target, boolean isInsertion) { + if (isInsertion) { + for (ITcObserver o : observers) { + o.tupleInserted(source, target); + } + } else { + for (ITcObserver o : observers) { + o.tupleDeleted(source, target); + } + } + } + + @Override + public void dispose() { + tc.clear(); + this.gds.detachObserver(this); + } + + @Override + public IGraphPathFinder getPathFinder() { + return new DFSPathFinder(gds, this); + } +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/counting/CountingTcRelation.java b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/counting/CountingTcRelation.java new file mode 100644 index 00000000..8f804dfd --- /dev/null +++ b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/counting/CountingTcRelation.java @@ -0,0 +1,259 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.base.itc.alg.counting; + +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import tools.refinery.viatra.runtime.base.itc.alg.misc.ITcRelation; +import tools.refinery.viatra.runtime.base.itc.alg.misc.topsort.TopologicalSorting; +import tools.refinery.viatra.runtime.base.itc.igraph.IBiDirectionalGraphDataSource; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType; +import tools.refinery.viatra.runtime.matchers.util.IMemoryView; +import tools.refinery.viatra.runtime.matchers.util.IMultiLookup; +import tools.refinery.viatra.runtime.matchers.util.IMultiLookup.ChangeGranularity; + +/** + * Transitive closure relation implementation for the Counting algorithm. + * + * @author Tamas Szabo + * + * @param + */ +public class CountingTcRelation implements ITcRelation { + + private IMultiLookup tuplesForward = null; + private IMultiLookup tuplesBackward = null; + + protected CountingTcRelation(boolean backwardIndexing) { + tuplesForward = CollectionsFactory.createMultiLookup(Object.class, MemoryType.MULTISETS, Object.class); + if (backwardIndexing) + tuplesBackward = CollectionsFactory.createMultiLookup(Object.class, MemoryType.MULTISETS, Object.class); + } + + protected boolean isEmpty() { + return 0 == this.tuplesForward.countKeys(); + } + + protected void clear() { + this.tuplesForward.clear(); + + if (tuplesBackward != null) { + this.tuplesBackward.clear(); + } + } + + protected void union(CountingTcRelation rA) { + IMultiLookup rForward = rA.tuplesForward; + for (V source : rForward.distinctKeys()) { + IMemoryView targetBag = rForward.lookup(source); + for (V target : targetBag.distinctValues()) { + this.addTuple(source, target, targetBag.getCount(target)); + } + } + } + + public int getCount(V source, V target) { + IMemoryView bucket = tuplesForward.lookup(source); + return bucket == null ? 0 : bucket.getCount(target); + } + + /** + * Returns true if the tc relation did not contain previously such a tuple that is defined by (source,target), false + * otherwise (in this case count is incremented with the given count parameter). + * + * @param source + * the source of the tuple + * @param target + * the target of the tuple + * @param count + * the count of the tuple, must be positive + * @return true if the relation did not contain previously the tuple + */ + public boolean addTuple(V source, V target, int count) { + if (tuplesBackward != null) { + tuplesBackward.addPairPositiveMultiplicity(target, source, count); + } + + ChangeGranularity change = + tuplesForward.addPairPositiveMultiplicity(source, target, count); + + return change != ChangeGranularity.DUPLICATE; + } + + /** + * Derivation count of the tuple (source,target) is incremented or decremented. + * Returns true iff updated to / from zero derivation count. + * @since 1.7 + */ + public boolean updateTuple(V source, V target, boolean isInsertion) { + if (isInsertion) { + if (tuplesBackward != null) { + tuplesBackward.addPair(target, source); + } + ChangeGranularity change = + tuplesForward.addPair(source, target); + return change != ChangeGranularity.DUPLICATE; + } else { + if (tuplesBackward != null) { + tuplesBackward.removePair(target, source); + } + ChangeGranularity change = + tuplesForward.removePair(source, target); + return change != ChangeGranularity.DUPLICATE; + } + } + + public void deleteTupleEnd(V deleted) { + Set sourcesToDelete = CollectionsFactory.createSet(); + Set targetsToDelete = CollectionsFactory.createSet(); + + for (V target : tuplesForward.lookupOrEmpty(deleted).distinctValues()) { + targetsToDelete.add(target); + } + if (tuplesBackward != null) { + for (V source : tuplesBackward.lookupOrEmpty(deleted).distinctValues()) { + sourcesToDelete.add(source); + } + } else { + for (V sourceCandidate : tuplesForward.distinctKeys()) { + if (tuplesForward.lookupOrEmpty(sourceCandidate).containsNonZero(deleted)) + sourcesToDelete.add(sourceCandidate); + } + } + + for (V source : sourcesToDelete) { + int count = tuplesForward.lookupOrEmpty(source).getCount(deleted); + for (int i=0; i< count; ++i) tuplesForward.removePair(source, deleted); + } + for (V target : targetsToDelete) { + int count = tuplesForward.lookupOrEmpty(deleted).getCount(target); + for (int i=0; i< count; ++i) tuplesForward.removePair(deleted, target); + } + + if (tuplesBackward != null) { + for (V source : sourcesToDelete) { + int count = tuplesBackward.lookupOrEmpty(deleted).getCount(source); + for (int i=0; i< count; ++i) tuplesBackward.removePair(deleted, source); + } + for (V target : targetsToDelete) { + int count = tuplesBackward.lookupOrEmpty(target).getCount(deleted); + for (int i=0; i< count; ++i) tuplesBackward.removePair(target, deleted); + } + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("TcRelation = "); + + for (V source : tuplesForward.distinctKeys()) { + IMemoryView targets = tuplesForward.lookup(source); + for (V target : targets.distinctValues()) { + sb.append("{(" + source + "," + target + ")," + targets.getCount(target) + "} "); + } + } + + return sb.toString(); + } + + @Override + public Set getTupleEnds(V source) { + IMemoryView tupEnds = tuplesForward.lookup(source); + if (tupEnds == null) + return null; + return tupEnds.distinctValues(); + } + + /** + * Returns the set of nodes from which the target node is reachable, if already computed. + * + * @param target + * the target node + * @return the set of source nodes + * @throws UnsupportedOperationException if backwards index not computed + */ + public Set getTupleStarts(V target) { + if (tuplesBackward != null) { + IMemoryView tupStarts = tuplesBackward.lookup(target); + if (tupStarts == null) + return null; + return tupStarts.distinctValues(); + } else { + throw new UnsupportedOperationException("built without backward indexing"); + } + } + + @Override + public Set getTupleStarts() { + Set nodes = CollectionsFactory.createSet(); + for (V s : tuplesForward.distinctKeys()) { + nodes.add(s); + } + return nodes; + } + + /** + * Returns true if a (source, target) node is present in the transitive closure relation, false otherwise. + * + * @param source + * the source node + * @param target + * the target node + * @return true if tuple is present, false otherwise + */ + public boolean containsTuple(V source, V target) { + return tuplesForward.lookupOrEmpty(source).containsNonZero(target); + } + + @SuppressWarnings("unchecked") + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (obj == null || this.getClass() != obj.getClass()) { + return false; + } else { + CountingTcRelation aTR = (CountingTcRelation) obj; + + return tuplesForward.equals(aTR.tuplesForward); + } + } + + @Override + public int hashCode() { + return tuplesForward.hashCode(); + } + + public static CountingTcRelation createFrom(IBiDirectionalGraphDataSource gds) { + List topologicalSorting = TopologicalSorting.compute(gds); + CountingTcRelation tc = new CountingTcRelation(true); + Collections.reverse(topologicalSorting); + for (V n : topologicalSorting) { + IMemoryView sourceNodes = gds.getSourceNodes(n); + Set tupEnds = tc.getTupleEnds(n); + for (V s : sourceNodes.distinctValues()) { + int count = sourceNodes.getCount(s); + for (int i = 0; i < count; i++) { + tc.updateTuple(s, n, true); + if (tupEnds != null) { + for (V t : tupEnds) { + tc.updateTuple(s, t, true); + } + } + } + } + } + + return tc; + } +} diff --git a/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/dred/DRedAlg.java b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/dred/DRedAlg.java new file mode 100644 index 00000000..b92d08bf --- /dev/null +++ b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/dred/DRedAlg.java @@ -0,0 +1,308 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.base.itc.alg.dred; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import tools.refinery.viatra.runtime.base.itc.alg.misc.DFSPathFinder; +import tools.refinery.viatra.runtime.base.itc.alg.misc.IGraphPathFinder; +import tools.refinery.viatra.runtime.base.itc.alg.misc.Tuple; +import tools.refinery.viatra.runtime.base.itc.alg.misc.dfs.DFSAlg; +import tools.refinery.viatra.runtime.base.itc.igraph.IGraphDataSource; +import tools.refinery.viatra.runtime.base.itc.igraph.IGraphObserver; +import tools.refinery.viatra.runtime.base.itc.igraph.ITcDataSource; +import tools.refinery.viatra.runtime.base.itc.igraph.ITcObserver; +import tools.refinery.viatra.runtime.matchers.util.IMemoryView; + +/** + * This class is the optimized implementation of the DRED algorithm. + * + * @author Tamas Szabo + * + * @param + * the type parameter of the nodes in the graph data source + */ +public class DRedAlg implements IGraphObserver, ITcDataSource { + + private IGraphDataSource graphDataSource = null; + private DRedTcRelation tc = null; + private DRedTcRelation dtc = null; + private List> observers; + + /** + * Constructs a new DRED algorithm and initializes the transitive closure relation with the given graph data source. + * Attach itself on the graph data source as an observer. + * + * @param gds + * the graph data source instance + */ + public DRedAlg(IGraphDataSource gds) { + this.observers = new ArrayList>(); + this.graphDataSource = gds; + this.tc = new DRedTcRelation(); + this.dtc = new DRedTcRelation(); + initTc(); + graphDataSource.attachObserver(this); + } + + /** + * Constructs a new DRED algorithm and initializes the transitive closure relation with the given relation. Attach + * itself on the graph data source as an observer. + * + * @param gds + * the graph data source instance + * @param tc + * the transitive closure instance + */ + public DRedAlg(IGraphDataSource gds, DRedTcRelation tc) { + this.graphDataSource = gds; + this.tc = tc; + this.dtc = new DRedTcRelation(); + graphDataSource.attachObserver(this); + } + + /** + * Initializes the transitive closure relation. + */ + private void initTc() { + DFSAlg dfsa = new DFSAlg(this.graphDataSource); + this.setTcRelation(dfsa.getTcRelation()); + this.graphDataSource.detachObserver(dfsa); + } + + @Override + public void edgeInserted(V source, V target) { + if (!source.equals(target)) { + Set tupStarts = null; + Set tupEnds = null; + Set> tuples = new HashSet>(); + + if (!source.equals(target) && tc.addTuple(source, target)) { + tuples.add(new Tuple(source, target)); + } + + // 2. d+(tc(x,y)) :- d+(tc(x,z)) & lv(z,y) Descartes product + tupStarts = tc.getTupleStarts(source); + tupEnds = tc.getTupleEnds(target); + + for (V s : tupStarts) { + for (V t : tupEnds) { + if (!s.equals(t) && tc.addTuple(s, t)) { + tuples.add(new Tuple(s, t)); + } + } + } + + // (s, source) -> (source, target) + // tupStarts = tc.getTupleStarts(source); + for (V s : tupStarts) { + if (!s.equals(target) && tc.addTuple(s, target)) { + tuples.add(new Tuple(s, target)); + } + } + + // (source, target) -> (target, t) + // tupEnds = tc.getTupleEnds(target); + for (V t : tupEnds) { + if (!source.equals(t) && tc.addTuple(source, t)) { + tuples.add(new Tuple(source, t)); + } + } + + notifyTcObservers(tuples, 1); + } + } + + @Override + public void edgeDeleted(V source, V target) { + if (!source.equals(target)) { + + // Computing overestimate, Descartes product of A and B sets, where + // A: those nodes from which source is reachable + // B: those nodes which is reachable from target + + Map, Integer> tuples = new HashMap, Integer>(); + Set sources = tc.getTupleStarts(source); + Set targets = tc.getTupleEnds(target); + + tc.removeTuple(source, target); + tuples.put(new Tuple(source, target), -1); + + for (V s : sources) { + for (V t : targets) { + if (!s.equals(t)) { + tc.removeTuple(s, t); + tuples.put(new Tuple(s, t), -1); + } + } + } + + for (V s : sources) { + if (!s.equals(target)) { + tc.removeTuple(s, target); + tuples.put(new Tuple(s, target), -1); + } + } + + for (V t : targets) { + if (!source.equals(t)) { + tc.removeTuple(source, t); + tuples.put(new Tuple(source, t), -1); + } + } + + // System.out.println("overestimate: "+dtc); + + // Modify overestimate with those tuples that have alternative derivations + // 1. q+(tc(x,y)) :- lv(x,y) + for (V s : graphDataSource.getAllNodes()) { + IMemoryView targetNodes = graphDataSource.getTargetNodes(s); + for (Entry entry : targetNodes.entriesWithMultiplicities()) { + for (int i = 0; i < entry.getValue(); i++) { + V t = entry.getKey(); + if (!s.equals(t)) { + tc.addTuple(s, t); + Tuple tuple = new Tuple(s, t); + Integer count = tuples.get(tuple); + if (count != null && count == -1) { + tuples.remove(tuple); + } + } + + } + } + } + + // 2. q+(tc(x,y)) :- tcv(x,z) & lv(z,y) + DRedTcRelation newTups = new DRedTcRelation(); + dtc.clear(); + dtc.union(tc); + + while (!dtc.isEmpty()) { + + newTups.clear(); + newTups.union(dtc); + dtc.clear(); + + for (V s : newTups.getTupleStarts()) { + for (V t : newTups.getTupleEnds(s)) { + IMemoryView targetNodes = graphDataSource.getTargetNodes(t); + if (targetNodes != null) { + for (Entry entry : targetNodes.entriesWithMultiplicities()) { + for (int i = 0; i < entry.getValue(); i++) { + V tn = entry.getKey(); + if (!s.equals(tn) && tc.addTuple(s, tn)) { + dtc.addTuple(s, tn); + tuples.remove(new Tuple(s, tn)); + } + } + } + } + } + } + } + + notifyTcObservers(tuples.keySet(), -1); + } + } + + @Override + public void nodeInserted(V n) { + // Node inserted does not result new tc tuple. + } + + @Override + public void nodeDeleted(V n) { + // FIXME node deletion may involve the deletion of incoming and outgoing edges too + Set set = tc.getTupleEnds(n); + Set modSet = null; + + // n -> target + modSet = new HashSet(set); + + for (V tn : modSet) { + this.tc.removeTuple(n, tn); + } + + // source -> n + set = tc.getTupleStarts(n); + + modSet = new HashSet(set); + + for (V sn : modSet) { + this.tc.removeTuple(sn, n); + } + } + + public DRedTcRelation getTcRelation() { + return this.tc; + } + + public void setTcRelation(DRedTcRelation tc) { + this.tc = tc; + } + + @Override + public boolean isReachable(V source, V target) { + return tc.containsTuple(source, target); + } + + @Override + public void attachObserver(ITcObserver to) { + this.observers.add(to); + } + + @Override + public void detachObserver(ITcObserver to) { + this.observers.remove(to); + } + + @Override + public Set getAllReachableTargets(V source) { + return tc.getTupleEnds(source); + } + + @Override + public Set getAllReachableSources(V target) { + return tc.getTupleStarts(target); + } + + protected void notifyTcObservers(Set> tuples, int dir) { + for (ITcObserver o : observers) { + for (Tuple t : tuples) { + if (!t.getSource().equals(t.getTarget())) { + if (dir == 1) { + o.tupleInserted(t.getSource(), t.getTarget()); + } + if (dir == -1) { + o.tupleDeleted(t.getSource(), t.getTarget()); + } + } + } + } + } + + @Override + public void dispose() { + tc = null; + dtc = null; + } + + @Override + public IGraphPathFinder getPathFinder() { + return new DFSPathFinder(graphDataSource, this); + } +} diff --git a/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/dred/DRedTcRelation.java b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/dred/DRedTcRelation.java new file mode 100644 index 00000000..8543b79c --- /dev/null +++ b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/dred/DRedTcRelation.java @@ -0,0 +1,223 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.base.itc.alg.dred; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import tools.refinery.viatra.runtime.base.itc.alg.misc.ITcRelation; + +public class DRedTcRelation implements ITcRelation { + + // tc(a,b) means that b is transitively reachable from a + private Map> tuplesForward; + + // data structure to efficiently get those nodes from which a given node is reachable + // symmetric to tuplesForward + private Map> tuplesBackward; + + public DRedTcRelation() { + this.tuplesForward = new HashMap>(); + this.tuplesBackward = new HashMap>(); + } + + public void clear() { + this.tuplesForward.clear(); + this.tuplesBackward.clear(); + } + + public boolean isEmpty() { + return tuplesForward.isEmpty(); + } + + public void removeTuple(V source, V target) { + + // removing tuple from 'forward' tc relation + Set sSet = tuplesForward.get(source); + if (sSet != null) { + sSet.remove(target); + if (sSet.size() == 0) + tuplesForward.remove(source); + } + + // removing tuple from 'backward' tc relation + Set tSet = tuplesBackward.get(target); + if (tSet != null) { + tSet.remove(source); + if (tSet.size() == 0) + tuplesBackward.remove(target); + } + } + + /** + * Returns true if the tc relation did not contain previously such a tuple that is defined by (source,target), false + * otherwise. + * + * @param source + * the source of the tuple + * @param target + * the target of the tuple + * @return true if the relation did not contain previously the tuple + */ + public boolean addTuple(V source, V target) { + + // symmetric modification, it is sufficient to check the return value in one collection + // adding tuple to 'forward' tc relation + Set sSet = tuplesForward.get(source); + if (sSet == null) { + Set newSet = new HashSet(); + newSet.add(target); + tuplesForward.put(source, newSet); + } else { + sSet.add(target); + } + + // adding tuple to 'backward' tc relation + Set tSet = tuplesBackward.get(target); + if (tSet == null) { + Set newSet = new HashSet(); + newSet.add(source); + tuplesBackward.put(target, newSet); + return true; + } else { + boolean ret = tSet.add(source); + return ret; + } + + } + + /** + * Union operation of two tc realtions. + * + * @param rA + * the other tc relation + */ + public void union(DRedTcRelation rA) { + for (V source : rA.tuplesForward.keySet()) { + for (V target : rA.tuplesForward.get(source)) { + this.addTuple(source, target); + } + } + } + + /** + * Computes the difference of this tc relation and the given rA parameter. + * + * @param rA + * the subtrahend relation + */ + public void difference(DRedTcRelation rA) { + for (V source : rA.tuplesForward.keySet()) { + for (V target : rA.tuplesForward.get(source)) { + this.removeTuple(source, target); + } + } + } + + @Override + public Set getTupleEnds(V source) { + Set t = tuplesForward.get(source); + return (t == null) ? new HashSet() : new HashSet(t); + } + + /** + * Returns the set of nodes from which the target node is reachable. + * + * @param target + * the target node + * @return the set of source nodes + */ + public Set getTupleStarts(V target) { + Set t = tuplesBackward.get(target); + return (t == null) ? new HashSet() : new HashSet(t); + } + + @Override + public Set getTupleStarts() { + Set t = tuplesForward.keySet(); + return new HashSet(t); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("TcRelation = "); + + for (Entry> entry : this.tuplesForward.entrySet()) { + V source = entry.getKey(); + for (V target : entry.getValue()) { + sb.append("(" + source + "," + target + ") "); + } + } + return sb.toString(); + } + + /** + * Returns true if a (source, target) node is present in the transitive closure relation, false otherwise. + * + * @param source + * the source node + * @param target + * the target node + * @return true if tuple is present, false otherwise + */ + public boolean containsTuple(V source, V target) { + if (tuplesForward.containsKey(source)) { + if (tuplesForward.get(source).contains(target)) + return true; + } + return false; + } + + @SuppressWarnings("unchecked") + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if ((obj == null) || (obj.getClass() != this.getClass())) { + return false; + } + + DRedTcRelation aTR = (DRedTcRelation) obj; + + for (Entry> entry : aTR.tuplesForward.entrySet()) { + V source = entry.getKey(); + for (V target : entry.getValue()) { + if (!this.containsTuple(source, target)) + return false; + } + } + + for (Entry> entry : this.tuplesForward.entrySet()) { + V source = entry.getKey(); + for (V target : entry.getValue()) { + if (!aTR.containsTuple(source, target)) + return false; + } + } + + return true; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 31 * hash + tuplesForward.hashCode(); + hash = 31 * hash + tuplesBackward.hashCode(); + return hash; + } + + public Map> getTuplesForward() { + return tuplesForward; + } +} diff --git a/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/fw/FloydWarshallAlg.java b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/fw/FloydWarshallAlg.java new file mode 100644 index 00000000..b369f1a1 --- /dev/null +++ b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/fw/FloydWarshallAlg.java @@ -0,0 +1,110 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.base.itc.alg.fw; + +import java.util.HashMap; +import java.util.Map; + +import tools.refinery.viatra.runtime.base.itc.alg.dred.DRedTcRelation; +import tools.refinery.viatra.runtime.base.itc.igraph.IBiDirectionalGraphDataSource; +import tools.refinery.viatra.runtime.base.itc.igraph.IBiDirectionalWrapper; +import tools.refinery.viatra.runtime.base.itc.igraph.IGraphDataSource; +import tools.refinery.viatra.runtime.base.itc.igraph.IGraphObserver; +import tools.refinery.viatra.runtime.matchers.util.IMemoryView; + +public class FloydWarshallAlg implements IGraphObserver { + + private DRedTcRelation tc = null; + private IBiDirectionalGraphDataSource gds = null; + + public FloydWarshallAlg(IGraphDataSource gds) { + if (gds instanceof IBiDirectionalGraphDataSource) { + this.gds = (IBiDirectionalGraphDataSource) gds; + } else { + this.gds = new IBiDirectionalWrapper(gds); + } + + this.tc = new DRedTcRelation(); + gds.attachObserver(this); + generateTc(); + } + + private void generateTc() { + + tc.clear(); + + int n = gds.getAllNodes().size(); + Map mapForw = new HashMap(); + Map mapBackw = new HashMap(); + int[][] P = new int[n][n]; + + int i, j, k; + + // initialize adjacent matrix + for (i = 0; i < n; i++) { + for (j = 0; j < n; j++) { + P[i][j] = 0; + } + } + + i = 0; + for (V node : gds.getAllNodes()) { + mapForw.put(node, i); + mapBackw.put(i, node); + i++; + } + + for (V source : gds.getAllNodes()) { + IMemoryView targets = gds.getTargetNodes(source); + for (V target : targets.distinctValues()) { + P[mapForw.get(source)][mapForw.get(target)] = 1; + } + } + + for (k = 0; k < n; k++) { + for (i = 0; i < n; i++) { + for (j = 0; j < n; j++) { + P[i][j] = P[i][j] | (P[i][k] & P[k][j]); + } + } + } + + for (i = 0; i < n; i++) { + for (j = 0; j < n; j++) { + if (P[i][j] == 1 && i != j) + tc.addTuple(mapBackw.get(i), mapBackw.get(j)); + } + } + } + + @Override + public void edgeInserted(V source, V target) { + generateTc(); + } + + @Override + public void edgeDeleted(V source, V target) { + generateTc(); + } + + @Override + public void nodeInserted(V n) { + generateTc(); + } + + @Override + public void nodeDeleted(V n) { + generateTc(); + } + + public DRedTcRelation getTcRelation() { + return this.tc; + } +} diff --git a/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/incscc/CountingListener.java b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/incscc/CountingListener.java new file mode 100644 index 00000000..fdf64f77 --- /dev/null +++ b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/incscc/CountingListener.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.base.itc.alg.incscc; + +import tools.refinery.viatra.runtime.base.itc.igraph.ITcObserver; +import tools.refinery.viatra.runtime.matchers.util.Direction; + +/** + * @author Tamas Szabo + * + */ +public class CountingListener implements ITcObserver { + + private IncSCCAlg alg; + + public CountingListener(IncSCCAlg alg) { + this.alg = alg; + } + + @Override + public void tupleInserted(V source, V target) { + alg.notifyTcObservers(alg.sccs.getPartition(source), alg.sccs.getPartition(target), Direction.INSERT); + } + + @Override + public void tupleDeleted(V source, V target) { + alg.notifyTcObservers(alg.sccs.getPartition(source), alg.sccs.getPartition(target), Direction.DELETE); + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/incscc/IncSCCAlg.java b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/incscc/IncSCCAlg.java new file mode 100644 index 00000000..f1e0ad44 --- /dev/null +++ b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/incscc/IncSCCAlg.java @@ -0,0 +1,645 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.base.itc.alg.incscc; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; + +import tools.refinery.viatra.runtime.base.itc.alg.counting.CountingAlg; +import tools.refinery.viatra.runtime.base.itc.alg.dred.DRedTcRelation; +import tools.refinery.viatra.runtime.base.itc.alg.misc.DFSPathFinder; +import tools.refinery.viatra.runtime.base.itc.alg.misc.GraphHelper; +import tools.refinery.viatra.runtime.base.itc.alg.misc.IGraphPathFinder; +import tools.refinery.viatra.runtime.base.itc.alg.misc.Tuple; +import tools.refinery.viatra.runtime.base.itc.alg.misc.bfs.BFS; +import tools.refinery.viatra.runtime.base.itc.alg.misc.scc.SCC; +import tools.refinery.viatra.runtime.base.itc.alg.misc.scc.SCCResult; +import tools.refinery.viatra.runtime.base.itc.alg.util.CollectionHelper; +import tools.refinery.viatra.runtime.base.itc.graphimpl.Graph; +import tools.refinery.viatra.runtime.base.itc.igraph.IBiDirectionalGraphDataSource; +import tools.refinery.viatra.runtime.base.itc.igraph.IBiDirectionalWrapper; +import tools.refinery.viatra.runtime.base.itc.igraph.IGraphDataSource; +import tools.refinery.viatra.runtime.base.itc.igraph.IGraphObserver; +import tools.refinery.viatra.runtime.base.itc.igraph.ITcDataSource; +import tools.refinery.viatra.runtime.base.itc.igraph.ITcObserver; +import tools.refinery.viatra.runtime.matchers.algorithms.UnionFind; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.IMemoryView; + +/** + * Incremental SCC maintenance + counting algorithm. + * + * @author Tamas Szabo + * + * @param + * the type parameter of the nodes in the graph data source + */ +public class IncSCCAlg implements IGraphObserver, ITcDataSource { + + public UnionFind sccs; + public IBiDirectionalGraphDataSource gds; + private CountingAlg counting; + private Graph reducedGraph; + private IBiDirectionalGraphDataSource reducedGraphIndexer; + private List> observers; + private CountingListener countingListener; + + public IncSCCAlg(IGraphDataSource graphDataSource) { + + if (graphDataSource instanceof IBiDirectionalGraphDataSource) { + gds = (IBiDirectionalGraphDataSource) graphDataSource; + } else { + gds = new IBiDirectionalWrapper(graphDataSource); + } + observers = CollectionsFactory.createObserverList(); + sccs = new UnionFind(); + reducedGraph = new Graph(); + reducedGraphIndexer = new IBiDirectionalWrapper(reducedGraph); + countingListener = new CountingListener(this); + initalizeInternalDataStructures(); + gds.attachObserver(this); + } + + private void initalizeInternalDataStructures() { + SCCResult _sccres = SCC.computeSCC(gds); + Set> _sccs = _sccres.getSccs(); + + for (Set _set : _sccs) { + sccs.makeSet(_set); + } + + // Initalization of the reduced graph + for (V n : sccs.getPartitionHeads()) { + reducedGraph.insertNode(n); + } + + for (V source : gds.getAllNodes()) { + final IMemoryView targetNodes = gds.getTargetNodes(source); + for (Entry entry : targetNodes.entriesWithMultiplicities()) { + for (int i = 0; i < entry.getValue(); i++) { + V target = entry.getKey(); + V sourceRoot = sccs.find(source); + V targetRoot = sccs.find(target); + + if (!sourceRoot.equals(targetRoot)) { + reducedGraph.insertEdge(sourceRoot, targetRoot); + } + } + } + } + + counting = new CountingAlg(reducedGraph); + } + + @Override + public void edgeInserted(V source, V target) { + V sourceRoot = sccs.find(source); + V targetRoot = sccs.find(target); + + // Different SCC + if (!sourceRoot.equals(targetRoot)) { + + // source is reachable from target? + if (counting.isReachable(targetRoot, sourceRoot)) { + + Set predecessorRoots = counting.getAllReachableSources(sourceRoot); + Set successorRoots = counting.getAllReachableTargets(targetRoot); + + // 1. intersection of source and target roots, these will be in the merged SCC + Set isectRoots = CollectionHelper.intersection(predecessorRoots, successorRoots); + isectRoots.add(sourceRoot); + isectRoots.add(targetRoot); + + // notifications must be issued before Union-Find modifications + if (observers.size() > 0) { + Set sourceSCCs = createSetNullTolerant(predecessorRoots); + sourceSCCs.add(sourceRoot); + Set targetSCCs = createSetNullTolerant(successorRoots); + targetSCCs.add(targetRoot); + + // tracing back to actual nodes + for (V sourceSCC : sourceSCCs) { + targetLoop: for (V targetSCC : targetSCCs) { + if (counting.isReachable(sourceSCC, targetSCC)) continue targetLoop; + + boolean needsNotification = + // Case 1. sourceSCC and targetSCC are the same and it is a one sized scc. + // Issue notifications only if there is no self-loop present at the moment + (sourceSCC.equals(targetSCC) && sccs.getPartition(sourceSCC).size() == 1 && GraphHelper + .getEdgeCount(sccs.getPartition(sourceSCC).iterator().next(), gds) == 0) + || + // Case 2. sourceSCC and targetSCC are different sccs. + (!sourceSCC.equals(targetSCC)); + // if self loop is already present omit the notification + if (needsNotification) { + notifyTcObservers(sccs.getPartition(sourceSCC), sccs.getPartition(targetSCC), + Direction.INSERT); + } + } + } + } + + // 2. delete edges, nodes + List sourceSCCs = new ArrayList(); + List targetSCCs = new ArrayList(); + + for (V r : isectRoots) { + List sourceSCCsOfSCC = getSourceSCCsOfSCC(r); + List targetSCCsOfSCC = getTargetSCCsOfSCC(r); + + for (V sourceSCC : sourceSCCsOfSCC) { + if (!sourceSCC.equals(r)) { + reducedGraph.deleteEdgeIfExists(sourceSCC, r); + } + } + + for (V targetSCC : targetSCCsOfSCC) { + if (!isectRoots.contains(targetSCC) && !r.equals(targetSCC)) { + reducedGraph.deleteEdgeIfExists(r, targetSCC); + } + } + + sourceSCCs.addAll(sourceSCCsOfSCC); + targetSCCs.addAll(targetSCCsOfSCC); + } + + for (V r : isectRoots) { + reducedGraph.deleteNode(r); + } + + // 3. union + Iterator iterator = isectRoots.iterator(); + V newRoot = iterator.next(); + while (iterator.hasNext()) { + newRoot = sccs.union(newRoot, iterator.next()); + } + + // 4. add new node + reducedGraph.insertNode(newRoot); + + // 5. add edges + Set containedNodes = sccs.getPartition(newRoot); + + for (V sourceSCC : sourceSCCs) { + if (!containedNodes.contains(sourceSCC) && !sourceSCC.equals(newRoot)) { + reducedGraph.insertEdge(sourceSCC, newRoot); + } + } + for (V targetSCC : targetSCCs) { + if (!containedNodes.contains(targetSCC) && !targetSCC.equals(newRoot)) { + reducedGraph.insertEdge(newRoot, targetSCC); + } + } + } else { + if (observers.size() > 0 && GraphHelper.getEdgeCount(source, target, gds) == 1) { + counting.attachObserver(countingListener); + } + reducedGraph.insertEdge(sourceRoot, targetRoot); + counting.detachObserver(countingListener); + } + } else { + // Notifications about self-loops + if (observers.size() > 0 && sccs.getPartition(sourceRoot).size() == 1 + && GraphHelper.getEdgeCount(source, target, gds) == 1) { + notifyTcObservers(source, source, Direction.INSERT); + } + } + } + + @Override + public void edgeDeleted(V source, V target) { + V sourceRoot = sccs.find(source); + V targetRoot = sccs.find(target); + + if (!sourceRoot.equals(targetRoot)) { + if (observers.size() > 0 && GraphHelper.getEdgeCount(source, target, gds) == 0) { + counting.attachObserver(countingListener); + } + reducedGraph.deleteEdgeIfExists(sourceRoot, targetRoot); + counting.detachObserver(countingListener); + } else { + // get the graph for the scc whose root is sourceRoot + Graph g = GraphHelper.getSubGraph(sccs.getPartition(sourceRoot), gds); + + // if source is not reachable from target anymore + if (!BFS.isReachable(source, target, g)) { + // create copies of the current state before destructive manipulation + Map reachableSources = CollectionsFactory.createMap(); + for (Entry entry : reducedGraphIndexer.getSourceNodes(sourceRoot).entriesWithMultiplicities()) { + reachableSources.put(entry.getKey(), entry.getValue()); + } + Map reachableTargets = CollectionsFactory.createMap(); + for (Entry entry : reducedGraphIndexer.getTargetNodes(sourceRoot).entriesWithMultiplicities()) { + reachableTargets.put(entry.getKey(), entry.getValue()); + } + + SCCResult _newSccs = SCC.computeSCC(g); + + // delete scc node (and with its edges too) + for (Entry entry : reachableSources.entrySet()) { + V s = entry.getKey(); + for (int i = 0; i < entry.getValue(); i++) { + reducedGraph.deleteEdgeIfExists(s, sourceRoot); + } + } + + for (Entry entry : reachableTargets.entrySet()) { + V t = entry.getKey(); + for (int i = 0; i < entry.getValue(); i++) { + reducedGraph.deleteEdgeIfExists(sourceRoot, t); + } + } + + sccs.deleteSet(sourceRoot); + reducedGraph.deleteNode(sourceRoot); + + Set> newSCCs = _newSccs.getSccs(); + Set newSCCRoots = CollectionsFactory.createSet(); + + // add new nodes and edges to the reduced graph + for (Set newSCC : newSCCs) { + V newRoot = sccs.makeSet(newSCC); + reducedGraph.insertNode(newRoot); + newSCCRoots.add(newRoot); + } + for (V newSCCRoot : newSCCRoots) { + List sourceSCCsOfSCC = getSourceSCCsOfSCC(newSCCRoot); + List targetSCCsOfSCC = getTargetSCCsOfSCC(newSCCRoot); + + for (V sourceSCC : sourceSCCsOfSCC) { + if (!sourceSCC.equals(newSCCRoot)) { + reducedGraph.insertEdge(sccs.find(sourceSCC), newSCCRoot); + } + } + for (V targetSCC : targetSCCsOfSCC) { + if (!newSCCRoots.contains(targetSCC) && !targetSCC.equals(newSCCRoot)) + reducedGraph.insertEdge(newSCCRoot, targetSCC); + } + } + + // Must be after the union-find modifications + if (observers.size() > 0) { + V newSourceRoot = sccs.find(source); + V newTargetRoot = sccs.find(target); + + Set sourceSCCs = createSetNullTolerant(counting.getAllReachableSources(newSourceRoot)); + sourceSCCs.add(newSourceRoot); + + Set targetSCCs = createSetNullTolerant(counting.getAllReachableTargets(newTargetRoot)); + targetSCCs.add(newTargetRoot); + + for (V sourceSCC : sourceSCCs) { + targetLoop: for (V targetSCC : targetSCCs) { + if (counting.isReachable(sourceSCC, targetSCC)) continue targetLoop; + + boolean needsNotification = + // Case 1. sourceSCC and targetSCC are the same and it is a one sized scc. + // Issue notifications only if there is no self-loop present at the moment + (sourceSCC.equals(targetSCC) && sccs.getPartition(sourceSCC).size() == 1 && GraphHelper + .getEdgeCount(sccs.getPartition(sourceSCC).iterator().next(), gds) == 0) + || + // Case 2. sourceSCC and targetSCC are different sccs. + (!sourceSCC.equals(targetSCC)); + // if self loop is already present omit the notification + if (needsNotification) { + notifyTcObservers(sccs.getPartition(sourceSCC), sccs.getPartition(targetSCC), + Direction.DELETE); + } + } + } + } + } else { + // only handle self-loop notifications - sourceRoot equals to targetRoot + if (observers.size() > 0 && sccs.getPartition(sourceRoot).size() == 1 + && GraphHelper.getEdgeCount(source, target, gds) == 0) { + notifyTcObservers(source, source, Direction.DELETE); + } + } + } + } + + @Override + public void nodeInserted(V n) { + sccs.makeSet(n); + reducedGraph.insertNode(n); + } + + @Override + public void nodeDeleted(V n) { + IMemoryView sources = gds.getSourceNodes(n); + IMemoryView targets = gds.getTargetNodes(n); + + for (Entry entry : sources.entriesWithMultiplicities()) { + for (int i = 0; i < entry.getValue(); i++) { + V source = entry.getKey(); + edgeDeleted(source, n); + } + } + + for (Entry entry : targets.entriesWithMultiplicities()) { + for (int i = 0; i < entry.getValue(); i++) { + V target = entry.getKey(); + edgeDeleted(n, target); + } + } + + sccs.deleteSet(n); + } + + @Override + public void attachObserver(ITcObserver to) { + observers.add(to); + } + + @Override + public void detachObserver(ITcObserver to) { + observers.remove(to); + } + + @Override + public Set getAllReachableTargets(V source) { + V sourceRoot = sccs.find(source); + Set containedNodes = sccs.getPartition(sourceRoot); + Set targets = CollectionsFactory.createSet(); + + if (containedNodes.size() > 1 || GraphHelper.getEdgeCount(source, gds) == 1) { + targets.addAll(containedNodes); + } + + Set rootSet = counting.getAllReachableTargets(sourceRoot); + if (rootSet != null) { + for (V _root : rootSet) { + targets.addAll(sccs.getPartition(_root)); + } + } + + return targets; + } + + @Override + public Set getAllReachableSources(V target) { + V targetRoot = sccs.find(target); + Set containedNodes = sccs.getPartition(targetRoot); + Set sources = CollectionsFactory.createSet(); + + if (containedNodes.size() > 1 || GraphHelper.getEdgeCount(target, gds) == 1) { + sources.addAll(containedNodes); + } + + Set rootSet = counting.getAllReachableSources(targetRoot); + if (rootSet != null) { + for (V _root : rootSet) { + sources.addAll(sccs.getPartition(_root)); + } + } + return sources; + } + + @Override + public boolean isReachable(V source, V target) { + V sourceRoot = sccs.find(source); + V targetRoot = sccs.find(target); + + if (sourceRoot.equals(targetRoot)) + return true; + else + return counting.isReachable(sourceRoot, targetRoot); + } + + public List getReachabilityPath(V source, V target) { + if (!isReachable(source, target)) { + return null; + } else { + Set sccsInSubGraph = CollectionHelper.intersection(counting.getAllReachableTargets(source), + counting.getAllReachableSources(target)); + sccsInSubGraph.add(sccs.find(source)); + sccsInSubGraph.add(sccs.find(target)); + Set nodesInSubGraph = CollectionsFactory.createSet(); + + for (V sccRoot : sccsInSubGraph) { + nodesInSubGraph.addAll(sccs.getPartition(sccRoot)); + } + + return GraphHelper.constructPath(source, target, nodesInSubGraph, gds); + } + } + + // for JUnit + public boolean checkTcRelation(DRedTcRelation tc) { + + for (V s : tc.getTupleStarts()) { + for (V t : tc.getTupleEnds(s)) { + if (!isReachable(s, t)) + return false; + } + } + + for (V root : counting.getTcRelation().getTupleStarts()) { + for (V end : counting.getTcRelation().getTupleEnds(root)) { + for (V s : sccs.getPartition(root)) { + for (V t : sccs.getPartition(end)) { + if (!tc.containsTuple(s, t)) + return false; + } + } + } + } + + return true; + } + + /** + * Return the SCCs from which the SCC represented by the root node is reachable. Note that an SCC can be present + * multiple times in the returned list (multiple edges between the two SCCs). + * + * @param root + * @return the list of reachable target SCCs + */ + private List getSourceSCCsOfSCC(V root) { + List sourceSCCs = new ArrayList(); + + for (V containedNode : this.sccs.getPartition(root)) { + IMemoryView sourceNodes = this.gds.getSourceNodes(containedNode); + for (V source : sourceNodes.distinctValues()) { + sourceSCCs.add(this.sccs.find(source)); + } + } + + return sourceSCCs; + } + + /** + * Returns true if the SCC represented by the given root node has incoming edges in the reduced graph, + * false otherwise (if this SCC is a source in the reduced graph). + * + * @param root the root node of an SCC + * @return true if it has incoming edges, false otherwise + * @since 1.6 + */ + public boolean hasIncomingEdges(final V root) { + for (final V containedNode : this.sccs.getPartition(root)) { + final IMemoryView sourceNodes = this.gds.getSourceNodes(containedNode); + for (final V source : sourceNodes.distinctValues()) { + final V otherRoot = this.sccs.find(source); + if (!Objects.equals(root, otherRoot)) { + return true; + } + } + } + return false; + } + + /** + * Return the SCCs which are reachable from the SCC represented by the root node. Note that an SCC can be present + * multiple times in the returned list (multiple edges between the two SCCs). + * + * @param root + * @return the list of reachable target SCCs + */ + private List getTargetSCCsOfSCC(V root) { + List targetSCCs = new ArrayList(); + + for (V containedNode : this.sccs.getPartition(root)) { + IMemoryView targetNodes = this.gds.getTargetNodes(containedNode); + for (V target : targetNodes.distinctValues()) { + targetSCCs.add(this.sccs.find(target)); + } + } + + return targetSCCs; + } + + /** + * Returns true if the SCC represented by the given root node has outgoing edges in the reduced graph, + * false otherwise (if this SCC is a sink in the reduced graph). + * + * @param root the root node of an SCC + * @return true if it has outgoing edges, false otherwise + * @since 1.6 + */ + public boolean hasOutgoingEdges(V root) { + for (final V containedNode : this.sccs.getPartition(root)) { + final IMemoryView targetNodes = this.gds.getTargetNodes(containedNode); + for (final V target : targetNodes.distinctValues()) { + final V otherRoot = this.sccs.find(target); + if (!Objects.equals(root, otherRoot)) { + return true; + } + } + } + return false; + } + + @Override + public void dispose() { + gds.detachObserver(this); + counting.dispose(); + } + + /** + * Call this method to notify the observers of the transitive closure relation. The tuples used in the notification + * will be the Descartes product of the two sets given. + * + * @param sources + * the source nodes + * @param targets + * the target nodes + * @param direction + */ + protected void notifyTcObservers(Set sources, Set targets, Direction direction) { + for (V s : sources) { + for (V t : targets) { + notifyTcObservers(s, t, direction); + } + } + } + + private void notifyTcObservers(V source, V target, Direction direction) { + for (ITcObserver observer : observers) { + if (direction == Direction.INSERT) { + observer.tupleInserted(source, target); + } + if (direction == Direction.DELETE) { + observer.tupleDeleted(source, target); + } + } + } + + /** + * Returns the node that is selected as the representative of the SCC containing the argument. + * @since 1.6 + */ + public V getRepresentative(V node) { + return sccs.find(node); + } + + public Set> getTcRelation() { + Set> resultSet = new HashSet>(); + + for (V sourceRoot : sccs.getPartitionHeads()) { + Set sources = sccs.getPartition(sourceRoot); + if (sources.size() > 1 || GraphHelper.getEdgeCount(sources.iterator().next(), gds) == 1) { + for (V source : sources) { + for (V target : sources) { + resultSet.add(new Tuple(source, target)); + } + } + } + + Set reachableTargets = counting.getAllReachableTargets(sourceRoot); + if (reachableTargets != null) { + for (V targetRoot : reachableTargets) { + for (V source : sources) { + for (V target : sccs.getPartition(targetRoot)) { + resultSet.add(new Tuple(source, target)); + } + } + } + } + } + + return resultSet; + } + + public boolean isIsolated(V node) { + IMemoryView targets = gds.getTargetNodes(node); + IMemoryView sources = gds.getSourceNodes(node); + return targets.isEmpty() && sources.isEmpty(); + } + + @Override + public IGraphPathFinder getPathFinder() { + return new DFSPathFinder(gds, this); + } + + /** + * The graph of SCCs; each SCC is represented by its representative node (see {@link #getRepresentative(Object)}) + * @since 1.6 + */ + public Graph getReducedGraph() { + return reducedGraph; + } + + private static Set createSetNullTolerant(Set initial) { + if (initial != null) + return CollectionsFactory.createSet(initial); + else + return CollectionsFactory.createSet(); + } + + +} diff --git a/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/DFSPathFinder.java b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/DFSPathFinder.java new file mode 100644 index 00000000..51017b1a --- /dev/null +++ b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/DFSPathFinder.java @@ -0,0 +1,146 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Abel Hegedus and IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.base.itc.alg.misc; + +import java.util.ArrayList; +import java.util.Deque; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import tools.refinery.viatra.runtime.base.itc.igraph.IGraphDataSource; +import tools.refinery.viatra.runtime.base.itc.igraph.ITcDataSource; +import tools.refinery.viatra.runtime.matchers.util.IMemoryView; + +/** + * A depth-first search implementation of the {@link IGraphPathFinder}. + * + * TODO use ITC to filter nodes that must be traversed, instead of checks + * + * @author Abel Hegedus + * + * @param + * the node type of the graph + */ +public class DFSPathFinder implements IGraphPathFinder { + + private IGraphDataSource graph; + private ITcDataSource itc; + + public DFSPathFinder(IGraphDataSource graph, ITcDataSource itc) { + this.graph = graph; + this.itc = itc; + } + + @Override + public Iterable> getAllPaths(V sourceNode, V targetNode) { + Set endNodes = new HashSet(); + endNodes.add(targetNode); + return getAllPathsToTargets(sourceNode, endNodes); + } + + @Override + public Iterable> getAllPathsToTargets(V sourceNode, Set targetNodes) { + List> paths = new ArrayList>(); + Deque visited = new LinkedList(); + Set reachableTargets = new HashSet(); + for (V targetNode : targetNodes) { + if (itc.isReachable(sourceNode, targetNode)) { + reachableTargets.add(targetNode); + } + } + if (!reachableTargets.isEmpty()) { + return paths; + } + visited.add(sourceNode); + return getPaths(paths, visited, reachableTargets); + } + + protected Iterable> getPaths(List> paths, Deque visited, Set targetNodes) { + IMemoryView nodes = graph.getTargetNodes(visited.getLast()); + // examine adjacent nodes + for (V node : nodes.distinctValues()) { + if (visited.contains(node)) { + continue; + } + if (targetNodes.contains(node)) { + visited.add(node); + // clone visited LinkedList + Deque visitedClone = new LinkedList(visited); + paths.add(visitedClone); + visited.removeLast(); + break; + } + } + + // in breadth-first, recursion needs to come after visiting connected nodes + for (V node : nodes.distinctValues()) { + if (visited.contains(node) || targetNodes.contains(node)) { + continue; + } + boolean canReachTarget = false; + for (V target : targetNodes) { + if (itc.isReachable(node, target)) { + canReachTarget = true; + break; + } + } + if (canReachTarget) { + visited.addLast(node); + getPaths(paths, visited, targetNodes); + visited.removeLast(); + } + } + + return paths; + } + + public String printPaths(List> paths) { + StringBuilder sb = new StringBuilder(); + for (Deque visited : paths) { + sb.append("Path: "); + for (V node : visited) { + sb.append(node); + sb.append(" --> "); + } + sb.append("\n"); + } + return sb.toString(); + } + + @Override + public Deque getPath(V sourceNode, V targetNode) { + // TODO optimize + Iterable> allPaths = getAllPaths(sourceNode, targetNode); + Iterator> pathIterator = allPaths.iterator(); + return pathIterator.hasNext() ? pathIterator.next() : new LinkedList(); + } + + @Override + public Iterable> getShortestPaths(V sourceNode, V targetNode) { + // TODO optimize + Iterable> allPaths = getAllPaths(sourceNode, targetNode); + List> shortestPaths = new ArrayList>(); + int shortestPathLength = -1; + for (Deque path : allPaths) { + int pathLength = path.size(); + if (shortestPathLength == -1 || pathLength < shortestPathLength) { + shortestPaths.clear(); + shortestPathLength = pathLength; + } + if (pathLength == shortestPathLength) { + shortestPaths.add(path); + } + } + return shortestPaths; + } + +} diff --git a/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/Edge.java b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/Edge.java new file mode 100644 index 00000000..cf68d36a --- /dev/null +++ b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/Edge.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.base.itc.alg.misc; + +public class Edge { + private V source; + private V target; + + public Edge(V source, V target) { + super(); + this.source = source; + this.target = target; + } + + public V getSource() { + return source; + } + + public void setSource(V source) { + this.source = source; + } + + public V getTarget() { + return target; + } + + public void setTarget(V target) { + this.target = target; + } + +} diff --git a/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/GraphHelper.java b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/GraphHelper.java new file mode 100644 index 00000000..194e979b --- /dev/null +++ b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/GraphHelper.java @@ -0,0 +1,173 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.base.itc.alg.misc; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map.Entry; +import java.util.Set; + +import tools.refinery.viatra.runtime.base.itc.graphimpl.Graph; +import tools.refinery.viatra.runtime.base.itc.igraph.IBiDirectionalGraphDataSource; +import tools.refinery.viatra.runtime.base.itc.igraph.IGraphDataSource; +import tools.refinery.viatra.runtime.matchers.util.IMemoryView; + +/** + * Utility class for graph related operations. + * + * @author Tamas Szabo + */ +public class GraphHelper { + + private GraphHelper() {/*Utility class constructor*/} + + /** + * Returns the subgraph from the given {@link IBiDirectionalGraphDataSource} which contains the given set of nodes. + * + * @param nodesInSubGraph + * the nodes that are present in the subgraph + * @param graphDataSource + * the graph data source for the original graph + * @return the subgraph associated to the given nodes + */ + public static Graph getSubGraph(Collection nodesInSubGraph, + IBiDirectionalGraphDataSource graphDataSource) { + Graph g = new Graph(); + if (nodesInSubGraph != null) { + for (V node : nodesInSubGraph) { + g.insertNode(node); + } + + for (V node : nodesInSubGraph) { + IMemoryView sources = graphDataSource.getSourceNodes(node); + for (Entry entry : sources.entriesWithMultiplicities()) { + for (int i = 0; i < entry.getValue(); i++) { + V s = entry.getKey(); + if (nodesInSubGraph.contains(s)) { + g.insertEdge(s, node); + } + } + } + } + } + + return g; + } + + /** + * Constructs a path between source and target in the given graph. Both the {@link IGraphDataSource} and the set of + * nodes are used, this way it is possible to construct a path in a given subgraph. + * + * The returned {@link List} contains the nodes along the path (this means that there is an edge in the graph + * between two consecutive nodes). A self loop (one edge) is indicated with the source node being present two times + * in the returned {@link List}. + * + * @param source + * the source node + * @param target + * the target node + * @param nodesInGraph + * the nodes that are present in the subgraph + * @param graphDataSource + * the graph data source + * @return the path between the two nodes + */ + public static List constructPath(V source, V target, Set nodesInGraph, + IGraphDataSource graphDataSource) { + Set visitedNodes = new HashSet(); + List path = new ArrayList(); + + visitedNodes.add(source); + path.add(source); + V act = source; + + // if source and target are the same node + if (source.equals(target) && graphDataSource.getTargetNodes(source).containsNonZero(target)) { + // the node will be present in the path two times + path.add(source); + return path; + } else { + while (act != null) { + V nextNode = getNextNodeToVisit(act, graphDataSource, nodesInGraph, visitedNodes); + if (nextNode == null && path.size() > 1) { + // needs to backtrack along path + // remove the last element in the path because we can't go + // anywhere from there + path.remove(path.size() - 1); + while (nextNode == null && path.size() > 0) { + V lastPathElement = path.get(path.size() - 1); + nextNode = getNextNodeToVisit(lastPathElement, graphDataSource, nodesInGraph, visitedNodes); + if (nextNode == null) { + path.remove(path.size() - 1); + } + } + } + + if (nextNode != null) { + visitedNodes.add(nextNode); + path.add(nextNode); + if (nextNode.equals(target)) { + return path; + } + } + act = nextNode; + } + return null; + } + } + + private static V getNextNodeToVisit(V act, IGraphDataSource graphDataSource, Set nodesInSubGraph, + Set visitedNodes) { + IMemoryView targetNodes = graphDataSource.getTargetNodes(act); + for (Entry entry : targetNodes.entriesWithMultiplicities()) { + for (int i = 0; i < entry.getValue(); i++) { + V node = entry.getKey(); + if (nodesInSubGraph.contains(node) && !visitedNodes.contains(node)) { + return node; + } + } + } + return null; + } + + /** + * Returns the number of self-loop edges for the given node. + * + * @param node + * the node + * @param graphDataSource + * the graph data source + * @return the number of self-loop edges + */ + public static int getEdgeCount(V node, IGraphDataSource graphDataSource) { + return getEdgeCount(node, node, graphDataSource); + } + + /** + * Returns the number of edges between the given source and target nodes. + * + * @param source + * the source node + * @param target + * the target node + * @param graphDataSource + * the graph data source + * @return the number of parallel edges between the two nodes + */ + public static int getEdgeCount(V source, V target, IGraphDataSource graphDataSource) { + Integer count = graphDataSource.getTargetNodes(source).getCount(target); + if (count == null) { + return 0; + } else { + return count; + } + } +} diff --git a/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/IGraphPathFinder.java b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/IGraphPathFinder.java new file mode 100644 index 00000000..cebb09c8 --- /dev/null +++ b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/IGraphPathFinder.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Abel Hegedus, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.base.itc.alg.misc; + +import java.util.Deque; +import java.util.Set; + +import tools.refinery.viatra.runtime.base.itc.igraph.ITcDataSource; + +/** + * The path finder provides methods for retrieving paths in a graph between a source node and one or more target nodes. + * Use {@link ITcDataSource#getPathFinder()} for instantiating. + * + * @author Abel Hegedus + * + * @param the node type of the graph + */ +public interface IGraphPathFinder { + + /** + * Returns an arbitrary path from the source node to the target node (if such exists). If there is no path + * between them, an empty collection is returned. + * + * @param sourceNode the source node of the path + * @param targetNode the target node of the path + * @return the path from the source to the target, or empty collection if target is not reachable from source. + */ + Deque getPath(V sourceNode, V targetNode); + + /** + * Returns the collection of shortest paths from the source node to the target node (if such exists). If there is no path + * between them, an empty collection is returned. + * + * @param sourceNode the source node of the path + * @param targetNode the target node of the path + * @return the collection of shortest paths from the source to the target, or empty collection if target is not reachable from source. + */ + Iterable> getShortestPaths(V sourceNode, V targetNode); + + /** + * Returns the collection of paths from the source node to the target node (if such exists). If there is no path + * between them, an empty collection is returned. + * + * @param sourceNode the source node of the path + * @param targetNode the target node of the path + * @return the collection of paths from the source to the target, or empty collection if target is not reachable from source. + */ + Iterable> getAllPaths(V sourceNode, V targetNode); + + /** + * Returns the collection of paths from the source node to any of the target nodes (if such exists). If there is no path + * between them, an empty collection is returned. + * + * @param sourceNode the source node of the path + * @param targetNodes the set of target nodes of the paths + * @return the collection of paths from the source to any of the targets, or empty collection if neither target is reachable from source. + */ + Iterable> getAllPathsToTargets(V sourceNode, Set targetNodes); + + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/ITcRelation.java b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/ITcRelation.java new file mode 100644 index 00000000..a41ff6c7 --- /dev/null +++ b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/ITcRelation.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.base.itc.alg.misc; + +import java.util.Set; + +public interface ITcRelation { + + /** + * Returns the starting nodes from a transitive closure relation. + * + * @return the set of starting nodes + */ + public Set getTupleStarts(); + + /** + * Returns the set of nodes that are reachable from the given node. + * + * @param start + * the starting node + * @return the set of reachable nodes + */ + public Set getTupleEnds(V start); +} diff --git a/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/Tuple.java b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/Tuple.java new file mode 100644 index 00000000..a2d54a59 --- /dev/null +++ b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/Tuple.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.base.itc.alg.misc; + +public class Tuple { + + private V source; + private V target; + + public Tuple(V source, V target) { + super(); + this.source = source; + this.target = target; + } + + public V getSource() { + return source; + } + + public void setSource(V source) { + this.source = source; + } + + public V getTarget() { + return target; + } + + public void setTarget(V target) { + this.target = target; + } + + @Override + public String toString() { + return "(" + source.toString() + "," + target.toString() + ")"; + } + + @Override + public boolean equals(Object o) { + if (o instanceof Tuple) { + Tuple t = (Tuple) o; + + if (t.getSource().equals(this.source) && t.getTarget().equals(this.target)) { + return true; + } + } + return false; + } + + @Override + public int hashCode() { + return source.hashCode() + target.hashCode(); + } +} diff --git a/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/bfs/BFS.java b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/bfs/BFS.java new file mode 100644 index 00000000..798f31d2 --- /dev/null +++ b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/bfs/BFS.java @@ -0,0 +1,151 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.base.itc.alg.misc.bfs; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import tools.refinery.viatra.runtime.base.itc.igraph.IBiDirectionalGraphDataSource; +import tools.refinery.viatra.runtime.base.itc.igraph.IGraphDataSource; + +public class BFS { + + private BFS() {/*Utility class constructor*/} + + /** + * Performs a breadth first search on the given graph to determine whether source is reachable from target. + * + * @param + * the type parameter of the nodes in the graph + * @param source + * the source node + * @param target + * the target node + * @param graph + * the graph data source + * @return true if source is reachable from target, false otherwise + */ + public static boolean isReachable(V source, V target, IGraphDataSource graph) { + List nodeQueue = new ArrayList(); + Set visited = new HashSet(); + + nodeQueue.add(source); + visited.add(source); + + boolean ret = _isReachable(target, graph, nodeQueue, visited); + return ret; + } + + private static boolean _isReachable(V target, IGraphDataSource graph, List nodeQueue, Set visited) { + + while (!nodeQueue.isEmpty()) { + V node = nodeQueue.remove(0); + for (V t : graph.getTargetNodes(node).distinctValues()){ + if (t.equals(target)) { + return true; + } + if (!visited.contains(t)) { + visited.add(t); + nodeQueue.add(t); + } + } + } + return false; + } + + public static Set reachableSources(IBiDirectionalGraphDataSource graph, V target) { + Set retSet = new HashSet(); + retSet.add(target); + List nodeQueue = new ArrayList(); + nodeQueue.add(target); + + _reachableSources(graph, nodeQueue, retSet); + + return retSet; + } + + private static void _reachableSources(IBiDirectionalGraphDataSource graph, List nodeQueue, + Set retSet) { + while (!nodeQueue.isEmpty()) { + V node = nodeQueue.remove(0); + for (V _node : graph.getSourceNodes(node).distinctValues()) { + if (!retSet.contains(_node)) { + retSet.add(_node); + nodeQueue.add(_node); + } + } + } + } + + public static Set reachableTargets(IGraphDataSource graph, V source) { + Set retSet = new HashSet(); + retSet.add(source); + List nodeQueue = new ArrayList(); + nodeQueue.add(source); + + _reachableTargets(graph, nodeQueue, retSet); + + return retSet; + } + + private static void _reachableTargets(IGraphDataSource graph, List nodeQueue, Set retSet) { + while (!nodeQueue.isEmpty()) { + V node = nodeQueue.remove(0); + + for (V _node : graph.getTargetNodes(node).distinctValues()) { + + if (!retSet.contains(_node)) { + retSet.add(_node); + nodeQueue.add(_node); + } + } + } + } + + /** + * Performs a breadth first search on the given graph and collects all the nodes along the path from source to + * target if such path exists. + * + * @param + * the type parameter of the nodes in the graph + * @param source + * the source node + * @param target + * the target node + * @param graph + * the graph data source + * @return the set of nodes along the path + */ + public static Set collectNodesAlongPath(V source, V target, IGraphDataSource graph) { + Set path = new HashSet(); + _collectNodesAlongPath(source, target, graph, path); + return path; + } + + private static boolean _collectNodesAlongPath(V node, V target, IGraphDataSource graph, Set path) { + + boolean res = false; + + // end recursion + if (node.equals(target)) { + path.add(node); + return true; + } else { + for (V _nodeT : graph.getTargetNodes(node).distinctValues()) { + res = (_collectNodesAlongPath(_nodeT, target, graph, path)) || res; + } + if (res) + path.add(node); + return res; + } + } +} diff --git a/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/dfs/DFSAlg.java b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/dfs/DFSAlg.java new file mode 100644 index 00000000..c8d25c4e --- /dev/null +++ b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/dfs/DFSAlg.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.base.itc.alg.misc.dfs; + +import java.util.HashMap; + +import tools.refinery.viatra.runtime.base.itc.alg.dred.DRedTcRelation; +import tools.refinery.viatra.runtime.base.itc.igraph.IGraphDataSource; +import tools.refinery.viatra.runtime.base.itc.igraph.IGraphObserver; + +public class DFSAlg implements IGraphObserver { + + private IGraphDataSource gds; + private DRedTcRelation tc; + private int[] visited; + private HashMap nodeMap; + + public DFSAlg(IGraphDataSource gds) { + this.gds = gds; + this.tc = new DRedTcRelation(); + gds.attachObserver(this); + deriveTc(); + } + + private void deriveTc() { + tc.clear(); + + this.visited = new int[gds.getAllNodes().size()]; + nodeMap = new HashMap(); + + int j = 0; + for (V n : gds.getAllNodes()) { + nodeMap.put(n, j); + j++; + } + + for (V n : gds.getAllNodes()) { + oneDFS(n, n); + initVisitedArray(); + } + } + + private void initVisitedArray() { + for (int i = 0; i < visited.length; i++) + visited[i] = 0; + } + + private void oneDFS(V act, V source) { + + if (!act.equals(source)) { + tc.addTuple(source, act); + } + + visited[nodeMap.get(act)] = 1; + + for (V t : gds.getTargetNodes(act).distinctValues()) { + if (visited[nodeMap.get(t)] == 0) { + oneDFS(t, source); + } + } + } + + public DRedTcRelation getTcRelation() { + return this.tc; + } + + @Override + public void edgeInserted(V source, V target) { + deriveTc(); + } + + @Override + public void edgeDeleted(V source, V target) { + deriveTc(); + } + + @Override + public void nodeInserted(V n) { + deriveTc(); + } + + @Override + public void nodeDeleted(V n) { + deriveTc(); + } +} diff --git a/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/scc/PKAlg.java b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/scc/PKAlg.java new file mode 100644 index 00000000..c99a48ab --- /dev/null +++ b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/scc/PKAlg.java @@ -0,0 +1,179 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.base.itc.alg.misc.scc; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import tools.refinery.viatra.runtime.base.itc.igraph.IBiDirectionalGraphDataSource; +import tools.refinery.viatra.runtime.base.itc.igraph.IBiDirectionalWrapper; +import tools.refinery.viatra.runtime.base.itc.igraph.IGraphDataSource; +import tools.refinery.viatra.runtime.base.itc.igraph.IGraphObserver; + +public class PKAlg implements IGraphObserver { + + /** + * Maps the nodes to their indicies. + */ + private Map node2index; + private Map index2node; + private Map node2mark; + + /** + * Maps the index of a node to the index in the topsort. + */ + private Map index2topsort; + private Map topsort2index; + + /** + * Index associated to the inserted nodes (incrementing with every insertion). + */ + private int index; + + /** + * Index within the topsort for the target node when edge insertion occurs. + */ + private int lower_bound; + + /** + * Index within the topsort for the source node when edge insertion occurs. + */ + private int upper_bound; + + private List RF; + private List RB; + private IBiDirectionalGraphDataSource gds; + + public PKAlg(IGraphDataSource gds) { + if (gds instanceof IBiDirectionalGraphDataSource) { + this.gds = (IBiDirectionalGraphDataSource) gds; + } else { + this.gds = new IBiDirectionalWrapper(gds); + } + + node2mark = new HashMap(); + node2index = new HashMap(); + index2node = new HashMap(); + index2topsort = new HashMap(); + topsort2index = new HashMap(); + index = 0; + + gds.attachObserver(this); + } + + @Override + public void edgeInserted(V source, V target) { + + RF = new ArrayList(); + RB = new ArrayList(); + + lower_bound = index2topsort.get(node2index.get(target)); + upper_bound = index2topsort.get(node2index.get(source)); + + if (lower_bound < upper_bound) { + dfsForward(target); + dfsBackward(source); + reorder(); + } + } + + private List getIndicies(List list) { + List indicies = new ArrayList(); + + for (V n : list) + indicies.add(index2topsort.get(node2index.get(n))); + + return indicies; + } + + private void reorder() { + + Collections.reverse(RB); + + // azon csomopontok indexei amelyek sorrendje nem jo + List L = getIndicies(RF); + L.addAll(getIndicies(RB)); + Collections.sort(L); + + for (int i = 0; i < RB.size(); i++) { + index2topsort.put(node2index.get(RB.get(i)), L.get(i)); + topsort2index.put(L.get(i), node2index.get(RB.get(i))); + } + + for (int i = 0; i < RF.size(); i++) { + index2topsort.put(node2index.get(RF.get(i)), L.get(i + RB.size())); + topsort2index.put(L.get(i + RB.size()), node2index.get(RF.get(i))); + } + } + + @SuppressWarnings("unused") + private List getTopSort() { + List topsort = new ArrayList(); + + for (int i : topsort2index.values()) { + topsort.add(index2node.get(i)); + } + + return topsort; + } + + private void dfsBackward(V node) { + node2mark.put(node, true); + RB.add(node); + + for (V sn : gds.getSourceNodes(node).distinctValues()) { + int top_id = index2topsort.get(node2index.get(sn)); + + if (!node2mark.get(sn) && lower_bound < top_id) + dfsBackward(sn); + } + } + + private void dfsForward(V node) { + node2mark.put(node, true); + RF.add(node); + + for (V tn : gds.getTargetNodes(node).distinctValues()) { + int top_id = index2topsort.get(node2index.get(tn)); + + if (top_id == upper_bound) + System.out.println("!!!Cycle detected!!!"); + else if (!node2mark.get(tn) && top_id < upper_bound) + dfsForward(tn); + } + } + + @Override + public void edgeDeleted(V source, V target) { + // Edge deletion does not affect topsort + } + + @Override + public void nodeInserted(V n) { + node2mark.put(n, false); + node2index.put(n, index); + index2node.put(index, n); + index2topsort.put(index, index); + topsort2index.put(index, index); + index++; + } + + @Override + public void nodeDeleted(V n) { + node2mark.remove(n); + int node_id = node2index.remove(n); + index2node.remove(node_id); + int top_id = index2topsort.remove(node_id); + topsort2index.remove(top_id); + } +} diff --git a/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/scc/SCC.java b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/scc/SCC.java new file mode 100644 index 00000000..8915998b --- /dev/null +++ b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/scc/SCC.java @@ -0,0 +1,146 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.base.itc.alg.misc.scc; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.Stack; + +import tools.refinery.viatra.runtime.base.itc.igraph.IGraphDataSource; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; + +/** + * Efficient algorithms to compute the Strongly Connected Components in a directed graph. + * + * @author Tamas Szabo + * + * @param + * the type parameter of the nodes in the graph + */ +public class SCC { + + private SCC() {/*Utility class constructor*/} + + public static long sccId = 0; + + /** + * Computes the SCCs for the given graph and returns them as a multiset. (Iterative version of Tarjan's algorithm) + * + * @param g + * the directed graph data source + * @return the set of SCCs + */ + public static SCCResult computeSCC(IGraphDataSource g) { + int index = 0; + Set> ret = new HashSet>(); + + // stores the lowlink and index information for the given node + Map nodeMap = CollectionsFactory.createMap(); + + // stores all target nodes of a given node - the list will be modified + Map> targetNodeMap = CollectionsFactory.createMap(); + + // stores those target nodes for a given node which have not been visited + Map> notVisitedMap = CollectionsFactory.createMap(); + + // stores the nodes during the traversal + Stack nodeStack = new Stack(); + + // stores the nodes which belong to an scc (there can be many sccs in the stack at the same time) + Stack sccStack = new Stack(); + + boolean sink = false, finishedTraversal = true; + + // initialize all nodes with 0 index and 0 lowlink + Set allNodes = g.getAllNodes(); + for (V n : allNodes) { + nodeMap.put(n, new SCCProperty(0, 0)); + } + + for (V n : allNodes) { + // if the node has not been visited yet + if (nodeMap.get(n).getIndex() == 0) { + nodeStack.push(n); + + while (!nodeStack.isEmpty()) { + V currentNode = nodeStack.peek(); + sink = false; + finishedTraversal = false; + SCCProperty prop = nodeMap.get(currentNode); + + if (nodeMap.get(currentNode).getIndex() == 0) { + index++; + sccStack.push(currentNode); + prop.setIndex(index); + prop.setLowlink(index); + + notVisitedMap.put(currentNode, new HashSet()); + + // storing the target nodes of the actual node + if (g.getTargetNodes(currentNode) != null) { + Set targets = g.getTargetNodes(currentNode).distinctValues(); + targetNodeMap.put(currentNode, CollectionsFactory.createSet(targets)); + } + } + + if (targetNodeMap.get(currentNode) != null) { + + // remove node from stack, the exploration of its children has finished + if (targetNodeMap.get(currentNode).size() == 0) { + targetNodeMap.remove(currentNode); + + nodeStack.pop(); + + for (V targetNode : g.getTargetNodes(currentNode).distinctValues()) { + if (notVisitedMap.get(currentNode).contains(targetNode)) { + prop.setLowlink(Math.min(prop.getLowlink(), nodeMap.get(targetNode).getLowlink())); + } else if (sccStack.contains(targetNode)) { + prop.setLowlink(Math.min(prop.getLowlink(), nodeMap.get(targetNode).getIndex())); + } + } + + finishedTraversal = true; + } else { + V targetNode = targetNodeMap.get(currentNode).iterator().next(); + targetNodeMap.get(currentNode).remove(targetNode); + // if the targetNode has not yet been visited push it to the stack + // and mark it in the notVisitedMap + if (nodeMap.get(targetNode).getIndex() == 0) { + notVisitedMap.get(currentNode).add(targetNode); + nodeStack.add(targetNode); + } + } + } + // if currentNode has no target nodes + else { + nodeStack.pop(); + sink = true; + } + + // create scc if node is a sink or an scc has been found + if ((sink || finishedTraversal) && (prop.getLowlink() == prop.getIndex())) { + Set sc = new HashSet(); + V targetNode = null; + + do { + targetNode = sccStack.pop(); + sc.add(targetNode); + } while (!targetNode.equals(currentNode)); + + ret.add(sc); + } + } + } + } + + return new SCCResult(ret, g); + } +} diff --git a/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/scc/SCCProperty.java b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/scc/SCCProperty.java new file mode 100644 index 00000000..b26e3d37 --- /dev/null +++ b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/scc/SCCProperty.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.base.itc.alg.misc.scc; + +public class SCCProperty { + private int index; + private int lowlink; + + public SCCProperty(int index, int lowlink) { + super(); + this.index = index; + this.lowlink = lowlink; + } + + public int getIndex() { + return index; + } + + public void setIndex(int index) { + this.index = index; + } + + public int getLowlink() { + return lowlink; + } + + public void setLowlink(int lowlink) { + this.lowlink = lowlink; + } +} diff --git a/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/scc/SCCResult.java b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/scc/SCCResult.java new file mode 100644 index 00000000..fde59d3b --- /dev/null +++ b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/scc/SCCResult.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.base.itc.alg.misc.scc; + +import java.util.Map.Entry; +import java.util.Set; + +import tools.refinery.viatra.runtime.base.itc.igraph.IGraphDataSource; + +public class SCCResult { + + private Set> sccs; + private IGraphDataSource gds; + + public SCCResult(Set> sccs, IGraphDataSource gds) { + this.sccs = sccs; + this.gds = gds; + } + + public Set> getSccs() { + return sccs; + } + + public int getSCCCount() { + return sccs.size(); + } + + public double getAverageNodeCount() { + double a = 0; + + for (Set s : sccs) { + a += s.size(); + } + + return a / sccs.size(); + } + + public double getAverageEdgeCount() { + long edgeSum = 0; + + for (Set scc : sccs) { + for (V source : scc) { + for (Entry entry : gds.getTargetNodes(source).entriesWithMultiplicities()) { + if (scc.contains(entry.getKey())) { + edgeSum += entry.getValue(); + } + } + } + } + + return (double) edgeSum / (double) sccs.size(); + } + + public int getBiggestSCCSize() { + int max = 0; + + for (Set scc : sccs) { + if (scc.size() > max) + max = scc.size(); + } + + return max; + } + + public long getSumOfSquares() { + long sum = 0; + + for (Set scc : sccs) { + sum += scc.size() * scc.size(); + } + + return sum; + } +} diff --git a/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/topsort/TopologicalSorting.java b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/topsort/TopologicalSorting.java new file mode 100644 index 00000000..dd18e6c8 --- /dev/null +++ b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/misc/topsort/TopologicalSorting.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.base.itc.alg.misc.topsort; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.Stack; + +import tools.refinery.viatra.runtime.base.itc.igraph.IGraphDataSource; + +/** + * @since 1.6 + */ +public class TopologicalSorting { + + private TopologicalSorting() {/*Utility class constructor*/} + + private static final class Pair { + public T element; + public boolean isParent; + + public Pair(final T element, final boolean isParent) { + this.element = element; + this.isParent = isParent; + } + } + + /** + * Returns a topological ordering for the given graph data source. + * Output format: if there is an a -> b (transitive) reachability, then node a will come before node b in the resulting list. + * + * @param gds the graph data source + * @return a topological ordering + */ + public static List compute(final IGraphDataSource gds) { + final Set visited = new HashSet(); + final LinkedList result = new LinkedList(); + final Stack> dfsStack = new Stack>(); + + for (final T node : gds.getAllNodes()) { + if (!visited.contains(node)) { + dfsStack.push(new Pair(node, false)); + } + + while (!dfsStack.isEmpty()) { + final Pair head = dfsStack.pop(); + final T source = head.element; + + if (head.isParent) { + // we have already seen source, push it to the resulting stack + result.addFirst(source); + } else { + // first time we see source, continue with its children + visited.add(source); + dfsStack.push(new Pair(source, true)); + + for (final T target : gds.getTargetNodes(source).distinctValues()) { + if (!visited.contains(target)) { + dfsStack.push(new Pair(target, false)); + } + } + } + } + } + + return result; + } +} diff --git a/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/representative/RepresentativeElectionAlgorithm.java b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/representative/RepresentativeElectionAlgorithm.java new file mode 100644 index 00000000..51015404 --- /dev/null +++ b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/representative/RepresentativeElectionAlgorithm.java @@ -0,0 +1,140 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.viatra.runtime.base.itc.alg.representative; + +import tools.refinery.viatra.runtime.base.itc.graphimpl.Graph; +import tools.refinery.viatra.runtime.base.itc.igraph.IGraphObserver; +import tools.refinery.viatra.runtime.matchers.util.Direction; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public abstract class RepresentativeElectionAlgorithm implements IGraphObserver { + protected final Graph graph; + protected final Map representatives = new HashMap<>(); + protected final Map> components = new HashMap<>(); + private RepresentativeObserver observer; + + protected RepresentativeElectionAlgorithm(Graph graph) { + this.graph = graph; + initializeComponents(); + graph.attachObserver(this); + } + + protected abstract void initializeComponents(); + + protected void initializeSet(Set set) { + var iterator = set.iterator(); + if (!iterator.hasNext()) { + // Set is empty. + return; + } + var representative = iterator.next(); + for (var node : set) { + var oldRepresentative = representatives.put(node, representative); + if (oldRepresentative != null && !representative.equals(oldRepresentative)) { + throw new IllegalStateException("Node %s is already in a set represented by %s, cannot add it to %s" + .formatted(node, oldRepresentative, set)); + } + } + components.put(representative, set); + } + + protected void merge(Object leftRepresentative, Object rightRepresentative) { + if (leftRepresentative.equals(rightRepresentative)) { + return; + } + var leftSet = getComponent(leftRepresentative); + var rightSet = getComponent(rightRepresentative); + if (leftSet.size() < rightSet.size()) { + merge(rightRepresentative, rightSet, leftRepresentative, leftSet); + } else { + merge(leftRepresentative, leftSet, rightRepresentative, rightSet); + } + } + + private void merge(Object preservedRepresentative, Set preservedSet, Object removedRepresentative, + Set removedSet) { + components.remove(removedRepresentative); + for (var node : removedSet) { + representatives.put(node, preservedRepresentative); + preservedSet.add(node); + notifyToObservers(node, removedRepresentative, preservedRepresentative); + } + } + + protected void assignNewRepresentative(Object oldRepresentative, Set set) { + var iterator = set.iterator(); + if (!iterator.hasNext()) { + return; + } + var newRepresentative = iterator.next(); + components.put(newRepresentative, set); + for (var node : set) { + var oldRepresentativeOfNode = representatives.put(node, newRepresentative); + if (!oldRepresentative.equals(oldRepresentativeOfNode)) { + throw new IllegalArgumentException("Node %s was not represented by %s but by %s" + .formatted(node, oldRepresentative, oldRepresentativeOfNode)); + } + notifyToObservers(node, oldRepresentative, newRepresentative); + } + } + + public void setObserver(RepresentativeObserver observer) { + this.observer = observer; + } + + public Map> getComponents() { + return components; + } + + public Object getRepresentative(Object node) { + return representatives.get(node); + } + + public Set getComponent(Object representative) { + return components.get(representative); + } + + public void dispose() { + graph.detachObserver(this); + } + + @Override + public void nodeInserted(Object n) { + var component = new HashSet<>(1); + component.add(n); + initializeSet(component); + notifyToObservers(n, n, Direction.INSERT); + } + + @Override + public void nodeDeleted(Object n) { + var representative = representatives.remove(n); + if (!representative.equals(n)) { + throw new IllegalStateException("Trying to delete node with dangling edges"); + } + components.remove(representative); + notifyToObservers(n, representative, Direction.DELETE); + } + + protected void notifyToObservers(Object node, Object oldRepresentative, Object newRepresentative) { + notifyToObservers(node, oldRepresentative, Direction.DELETE); + notifyToObservers(node, newRepresentative, Direction.INSERT); + } + + protected void notifyToObservers(Object node, Object representative, Direction direction) { + if (observer != null) { + observer.tupleChanged(node, representative, direction); + } + } + + public interface Factory { + RepresentativeElectionAlgorithm create(Graph graph); + } +} diff --git a/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/representative/RepresentativeObserver.java b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/representative/RepresentativeObserver.java new file mode 100644 index 00000000..93cce1ea --- /dev/null +++ b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/representative/RepresentativeObserver.java @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.viatra.runtime.base.itc.alg.representative; + +import tools.refinery.viatra.runtime.matchers.util.Direction; + +public interface RepresentativeObserver { + void tupleChanged(Object node, Object representative, Direction direction); +} diff --git a/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/representative/StronglyConnectedComponentAlgorithm.java b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/representative/StronglyConnectedComponentAlgorithm.java new file mode 100644 index 00000000..ba42bb13 --- /dev/null +++ b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/representative/StronglyConnectedComponentAlgorithm.java @@ -0,0 +1,66 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.viatra.runtime.base.itc.alg.representative; + +import tools.refinery.viatra.runtime.base.itc.alg.misc.GraphHelper; +import tools.refinery.viatra.runtime.base.itc.alg.misc.bfs.BFS; +import tools.refinery.viatra.runtime.base.itc.alg.misc.scc.SCC; +import tools.refinery.viatra.runtime.base.itc.graphimpl.Graph; + +import java.util.Collection; +import java.util.Set; + +public class StronglyConnectedComponentAlgorithm extends RepresentativeElectionAlgorithm { + public StronglyConnectedComponentAlgorithm(Graph graph) { + super(graph); + } + + @Override + protected void initializeComponents() { + var computedSCCs = SCC.computeSCC(graph).getSccs(); + for (var computedSCC : computedSCCs) { + initializeSet(computedSCC); + } + } + + @Override + public void edgeInserted(Object source, Object target) { + var sourceRoot = getRepresentative(source); + var targetRoot = getRepresentative(target); + if (sourceRoot.equals(targetRoot)) { + // New edge does not change strongly connected components. + return; + } + if (BFS.isReachable(target, source, graph)) { + merge(sourceRoot, targetRoot); + } + } + + @Override + public void edgeDeleted(Object source, Object target) { + var sourceRoot = getRepresentative(source); + var targetRoot = getRepresentative(target); + if (!sourceRoot.equals(targetRoot)) { + // New edge does not change strongly connected components. + return; + } + var component = GraphHelper.getSubGraph(getComponent(sourceRoot), graph); + if (!BFS.isReachable(source, target, component)) { + var newSCCs = SCC.computeSCC(component).getSccs(); + split(sourceRoot, newSCCs); + } + } + + private void split(Object preservedRepresentative, Collection> sets) { + for (var set : sets) { + if (set.contains(preservedRepresentative)) { + components.put(preservedRepresentative, set); + } else { + assignNewRepresentative(preservedRepresentative, set); + } + } + } +} diff --git a/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/representative/WeaklyConnectedComponentAlgorithm.java b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/representative/WeaklyConnectedComponentAlgorithm.java new file mode 100644 index 00000000..22159499 --- /dev/null +++ b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/representative/WeaklyConnectedComponentAlgorithm.java @@ -0,0 +1,85 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.viatra.runtime.base.itc.alg.representative; + +import tools.refinery.viatra.runtime.base.itc.graphimpl.Graph; + +import java.util.ArrayDeque; +import java.util.HashSet; +import java.util.Set; + +public class WeaklyConnectedComponentAlgorithm extends RepresentativeElectionAlgorithm { + public WeaklyConnectedComponentAlgorithm(Graph graph) { + super(graph); + } + + @Override + protected void initializeComponents() { + for (var node : graph.getAllNodes()) { + if (representatives.containsKey(node)) { + continue; + } + var reachable = getReachableNodes(node); + initializeSet(reachable); + } + } + + @Override + public void edgeInserted(Object source, Object target) { + var sourceRoot = getRepresentative(source); + var targetRoot = getRepresentative(target); + merge(sourceRoot, targetRoot); + } + + @Override + public void edgeDeleted(Object source, Object target) { + var sourceRoot = getRepresentative(source); + var targetRoot = getRepresentative(target); + if (!sourceRoot.equals(targetRoot)) { + throw new IllegalArgumentException("Trying to remove edge not in graph"); + } + var targetReachable = getReachableNodes(target); + if (!targetReachable.contains(source)) { + split(sourceRoot, targetReachable); + } + } + + private void split(Object sourceRepresentative, Set targetReachable) { + var sourceComponent = getComponent(sourceRepresentative); + sourceComponent.removeAll(targetReachable); + if (targetReachable.contains(sourceRepresentative)) { + components.put(sourceRepresentative, targetReachable); + assignNewRepresentative(sourceRepresentative, sourceComponent); + } else { + assignNewRepresentative(sourceRepresentative, targetReachable); + } + } + + private Set getReachableNodes(Object source) { + var retSet = new HashSet<>(); + retSet.add(source); + var nodeQueue = new ArrayDeque<>(); + nodeQueue.addLast(source); + + while (!nodeQueue.isEmpty()) { + var node = nodeQueue.removeFirst(); + for (var neighbor : graph.getTargetNodes(node).distinctValues()) { + if (!retSet.contains(neighbor)) { + retSet.add(neighbor); + nodeQueue.addLast(neighbor); + } + } + for (var neighbor : graph.getSourceNodes(node).distinctValues()) { + if (!retSet.contains(neighbor)) { + retSet.add(neighbor); + nodeQueue.addLast(neighbor); + } + } + } + + return retSet; + } +} diff --git a/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/util/CollectionHelper.java b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/util/CollectionHelper.java new file mode 100644 index 00000000..c9b3cafa --- /dev/null +++ b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/alg/util/CollectionHelper.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.base.itc.alg.util; + +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; + +/** + * @author Tamas Szabo + * + */ +public class CollectionHelper { + + private CollectionHelper() {/*Utility class constructor*/} + + /** + * Returns the intersection of two sets. It calls {@link Set#retainAll(java.util.Collection)} but returns a new set + * containing the elements of the intersection. + * + * @param set1 + * the first set (can be null, interpreted as empty) + * @param set2 + * the second set (can be null, interpreted as empty) + * @return the intersection of the sets + * @since 1.7 + */ + public static Set intersection(Set set1, Set set2) { + if (set1 == null || set2 == null) + return CollectionsFactory.createSet(); + + Set intersection = CollectionsFactory.createSet(set1); + intersection.retainAll(set2); + return intersection; + } + + + /** + * Returns the difference of two sets (S1\S2). It calls {@link Set#removeAll(java.util.Collection)} but returns a + * new set containing the elements of the difference. + * + * @param set1 + * the first set (can be null, interpreted as empty) + * @param set2 + * the second set (can be null, interpreted as empty) + * @return the difference of the sets + * @since 1.7 + */ + public static Set difference(Set set1, Set set2) { + if (set1 == null) + return CollectionsFactory.createSet(); + + Set difference = CollectionsFactory.createSet(set1); + if (set2 != null) difference.removeAll(set2); + return difference; + } + +} diff --git a/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/graphimpl/DotGenerator.java b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/graphimpl/DotGenerator.java new file mode 100644 index 00000000..0e21f323 --- /dev/null +++ b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/graphimpl/DotGenerator.java @@ -0,0 +1,160 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.base.itc.graphimpl; + +import tools.refinery.viatra.runtime.base.itc.alg.misc.scc.SCC; +import tools.refinery.viatra.runtime.base.itc.alg.misc.scc.SCCResult; +import tools.refinery.viatra.runtime.matchers.util.IMemoryView; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + +/** + * This class contains utility methods to generate dot representations for {@link Graph} instances. + * + * @author Tamas Szabo + * @since 2.3 + */ +public class DotGenerator { + + private static final String[] colors = new String[] { "yellow", "blue", "red", "green", "gray", "cyan" }; + + private DotGenerator() { + + } + + /** + * Generates the dot representation for the given graph. + * + * @param graph + * the graph + * @param colorSCCs + * specifies if the strongly connected components with size greater than shall be colored + * @param nameFunction + * use this function to provide custom names to nodes, null if the default toString shall be used + * @param colorFunction + * use this function to provide custom color to nodes, null if the default white color shall be used + * @param edgeFunction + * use this function to provide custom edge labels, null if no edge label shall be printed + * @return the dot representation as a string + */ + public static String generateDot(final Graph graph, final boolean colorSCCs, + final Function nameFunction, final Function colorFunction, + final Function> edgeFunction) { + final Map colorMap = new HashMap(); + + if (colorSCCs) { + final SCCResult result = SCC.computeSCC(graph); + final Set> sccs = result.getSccs(); + + int i = 0; + for (final Set scc : sccs) { + if (scc.size() > 1) { + for (final V node : scc) { + final String color = colorMap.get(node); + if (color == null) { + colorMap.put(node, colors[i % colors.length]); + } else { + colorMap.put(node, colorMap.get(node) + ":" + colors[i % colors.length]); + } + } + i++; + } + } + + // if a node has no color yet, then make it white + for (final V node : graph.getAllNodes()) { + if (!colorMap.containsKey(node)) { + colorMap.put(node, "white"); + } + } + } else { + for (final V node : graph.getAllNodes()) { + colorMap.put(node, "white"); + } + } + + if (colorFunction != null) { + for (final V node : graph.getAllNodes()) { + colorMap.put(node, colorFunction.apply(node)); + } + } + + final StringBuilder builder = new StringBuilder(); + builder.append("digraph g {\n"); + + for (final V node : graph.getAllNodes()) { + final String nodePresentation = nameFunction == null ? node.toString() : nameFunction.apply(node); + builder.append("\"" + nodePresentation + "\""); + builder.append("[style=filled,fillcolor=" + colorMap.get(node) + "]"); + builder.append(";\n"); + } + + for (final V source : graph.getAllNodes()) { + final IMemoryView targets = graph.getTargetNodes(source); + if (!targets.isEmpty()) { + final String sourcePresentation = nameFunction == null ? source.toString() : nameFunction.apply(source); + for (final V target : targets.distinctValues()) { + String edgeLabel = null; + if (edgeFunction != null) { + final Function v1 = edgeFunction.apply(source); + if (v1 != null) { + edgeLabel = v1.apply(target); + } + } + + final String targetPresentation = nameFunction == null ? target.toString() + : nameFunction.apply(target); + + builder.append("\"" + sourcePresentation + "\" -> \"" + targetPresentation + "\""); + if (edgeLabel != null) { + builder.append("[label=\"" + edgeLabel + "\"]"); + } + builder.append(";\n"); + } + } + } + + builder.append("}"); + return builder.toString(); + } + + /** + * Generates the dot representation for the given graph. No special pretty printing customization will be applied. + * + * @param graph + * the graph + * @return the dot representation as a string + */ + public static String generateDot(final Graph graph) { + return generateDot(graph, false, null, null, null); + } + + /** + * Returns a simple name shortener function that can be used in the graphviz visualization to help with readability. + * WARNING: if you shorten the name of the {@link Node}s too much, the visualization may become incorrect because + * grahpviz will treat different nodes as the same if their shortened names are the same. + * + * @param maxLength + * the maximum length of the text that is kept from the toString of the objects in the graph + * @return the shrunk toString value + */ + public static Function getNameShortener(final int maxLength) { + return new Function() { + @Override + public String apply(final V obj) { + final String value = obj.toString(); + return value.substring(0, Math.min(value.length(), maxLength)); + } + }; + } + +} diff --git a/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/graphimpl/Graph.java b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/graphimpl/Graph.java new file mode 100644 index 00000000..70cbc77e --- /dev/null +++ b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/graphimpl/Graph.java @@ -0,0 +1,185 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.base.itc.graphimpl; + +import tools.refinery.viatra.runtime.base.itc.igraph.IBiDirectionalGraphDataSource; +import tools.refinery.viatra.runtime.base.itc.igraph.IGraphDataSource; +import tools.refinery.viatra.runtime.base.itc.igraph.IGraphObserver; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType; +import tools.refinery.viatra.runtime.matchers.util.IMemoryView; +import tools.refinery.viatra.runtime.matchers.util.IMultiLookup; + +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +public class Graph implements IGraphDataSource, IBiDirectionalGraphDataSource { + + // source -> target -> count + private IMultiLookup outgoingEdges; + // target -> source -> count + private IMultiLookup incomingEdges; + + private Set nodes; + + private List> observers; + + public Graph() { + outgoingEdges = CollectionsFactory.createMultiLookup(Object.class, MemoryType.MULTISETS, Object.class); + incomingEdges = CollectionsFactory.createMultiLookup(Object.class, MemoryType.MULTISETS, Object.class); + nodes = CollectionsFactory.createSet(); + observers = CollectionsFactory.createObserverList(); + } + + public void insertEdge(V source, V target) { + outgoingEdges.addPair(source, target); + incomingEdges.addPair(target, source); + + for (IGraphObserver go : observers) { + go.edgeInserted(source, target); + } + } + + /** + * No-op if trying to delete edge that does not exist + * + * @since 2.0 + * @see #deleteEdgeIfExists(Object, Object) + */ + public void deleteEdgeIfExists(V source, V target) { + boolean containedEdge = outgoingEdges.lookupOrEmpty(source).containsNonZero(target); + if (containedEdge) { + deleteEdgeThatExists(source, target); + } + } + + /** + * @throws IllegalStateException + * if trying to delete edge that does not exist + * @since 2.0 + * @see #deleteEdgeIfExists(Object, Object) + */ + public void deleteEdgeThatExists(V source, V target) { + outgoingEdges.removePair(source, target); + incomingEdges.removePair(target, source); + for (IGraphObserver go : observers) { + go.edgeDeleted(source, target); + } + } + + /** + * @deprecated use explicitly {@link #deleteEdgeThatExists(Object, Object)} or + * {@link #deleteEdgeIfExists(Object, Object)} instead. To preserve backwards compatibility, this method + * delegates to the latter. + * + */ + @Deprecated + public void deleteEdge(V source, V target) { + deleteEdgeIfExists(source, target); + } + + /** + * Insert the given node into the graph. + */ + public void insertNode(V node) { + if (nodes.add(node)) { + for (IGraphObserver go : observers) { + go.nodeInserted(node); + } + } + } + + /** + * Deletes the given node AND all of the edges going in and out from the node. + */ + public void deleteNode(V node) { + if (nodes.remove(node)) { + IMemoryView incomingView = incomingEdges.lookup(node); + if (incomingView != null) { + Map incoming = CollectionsFactory.createMap(incomingView.asMap()); + + for (Entry entry : incoming.entrySet()) { + for (int i = 0; i < entry.getValue(); i++) { + deleteEdgeThatExists(entry.getKey(), node); + } + } + } + + IMemoryView outgoingView = outgoingEdges.lookup(node); + if (outgoingView != null) { + Map outgoing = CollectionsFactory.createMap(outgoingView.asMap()); + + for (Entry entry : outgoing.entrySet()) { + for (int i = 0; i < entry.getValue(); i++) { + deleteEdgeThatExists(node, entry.getKey()); + } + } + } + + for (IGraphObserver go : observers) { + go.nodeDeleted(node); + } + } + } + + @Override + public void attachObserver(IGraphObserver go) { + observers.add(go); + } + + @Override + public void attachAsFirstObserver(IGraphObserver observer) { + observers.add(0, observer); + } + + @Override + public void detachObserver(IGraphObserver go) { + observers.remove(go); + } + + @Override + public Set getAllNodes() { + return nodes; + } + + @Override + public IMemoryView getTargetNodes(V source) { + return outgoingEdges.lookupOrEmpty(source); + } + + @Override + public IMemoryView getSourceNodes(V target) { + return incomingEdges.lookupOrEmpty(target); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("nodes = "); + for (V n : getAllNodes()) { + sb.append(n.toString()); + sb.append(" "); + } + sb.append(" edges = "); + for (V source : outgoingEdges.distinctKeys()) { + IMemoryView targets = outgoingEdges.lookup(source); + for (V target : targets.distinctValues()) { + int count = targets.getCount(target); + for (int i = 0; i < count; i++) { + sb.append("(" + source + "," + target + ") "); + } + } + } + return sb.toString(); + } + +} diff --git a/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/igraph/IBiDirectionalGraphDataSource.java b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/igraph/IBiDirectionalGraphDataSource.java new file mode 100644 index 00000000..64659447 --- /dev/null +++ b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/igraph/IBiDirectionalGraphDataSource.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.base.itc.igraph; + +import tools.refinery.viatra.runtime.matchers.util.IMemoryView; +import tools.refinery.viatra.runtime.matchers.util.IMultiset; + +/** + * A bi-directional graph data source supports all operations that an {@link IGraphDataSource} does, but it + * also makes it possible to query the incoming edges of nodes, not only the outgoing edges. + * + * @author Tamas Szabo + * + * @param the type of the nodes in the graph + */ +public interface IBiDirectionalGraphDataSource extends IGraphDataSource { + + /** + * Returns the source nodes for the given target node. + * The returned data structure is an {@link IMultiset} because of potential parallel edges in the graph data source. + * + * The method must not return null. + * + * @param target the target node + * @return the multiset of source nodes + * @since 2.0 + */ + public IMemoryView getSourceNodes(V target); + +} diff --git a/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/igraph/IBiDirectionalWrapper.java b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/igraph/IBiDirectionalWrapper.java new file mode 100644 index 00000000..becab0eb --- /dev/null +++ b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/igraph/IBiDirectionalWrapper.java @@ -0,0 +1,110 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.base.itc.igraph; + +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType; +import tools.refinery.viatra.runtime.matchers.util.IMemoryView; +import tools.refinery.viatra.runtime.matchers.util.IMultiLookup; + +/** + * This class can be used to wrap an {@link IGraphDataSource} into an {@link IBiDirectionalGraphDataSource}. This class + * provides support for the retrieval of source nodes for a given target which is not supported by standard + * {@link IGraphDataSource} implementations. + * + * @author Tamas Szabo + * + * @param + * the type parameter of the nodes in the graph data source + */ +public class IBiDirectionalWrapper implements IBiDirectionalGraphDataSource, IGraphObserver { + + private IGraphDataSource wrappedDataSource; + // target -> source -> count + private IMultiLookup incomingEdges; + + public IBiDirectionalWrapper(IGraphDataSource gds) { + this.wrappedDataSource = gds; + + this.incomingEdges = CollectionsFactory.createMultiLookup( + Object.class, MemoryType.MULTISETS, Object.class); + + if (gds.getAllNodes() != null) { + for (V source : gds.getAllNodes()) { + IMemoryView targets = gds.getTargetNodes(source); + for (V target : targets.distinctValues()) { + int count = targets.getCount(target); + for (int i = 0; i < count; i++) { + edgeInserted(source, target); + } + } + } + } + + gds.attachAsFirstObserver(this); + } + + @Override + public void attachObserver(IGraphObserver observer) { + wrappedDataSource.attachObserver(observer); + } + + @Override + public void attachAsFirstObserver(IGraphObserver observer) { + wrappedDataSource.attachAsFirstObserver(observer); + } + + @Override + public void detachObserver(IGraphObserver observer) { + wrappedDataSource.detachObserver(observer); + } + + @Override + public Set getAllNodes() { + return wrappedDataSource.getAllNodes(); + } + + @Override + public IMemoryView getTargetNodes(V source) { + return wrappedDataSource.getTargetNodes(source); + } + + @Override + public IMemoryView getSourceNodes(V target) { + return incomingEdges.lookupOrEmpty(target); + } + + @Override + public void edgeInserted(V source, V target) { + incomingEdges.addPair(target, source); + } + + @Override + public void edgeDeleted(V source, V target) { + incomingEdges.removePair(target, source); + } + + @Override + public void nodeInserted(V n) { + + } + + @Override + public void nodeDeleted(V node) { + + } + + @Override + public String toString() { + return wrappedDataSource.toString(); + } +} diff --git a/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/igraph/IGraphDataSource.java b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/igraph/IGraphDataSource.java new file mode 100644 index 00000000..3fa65936 --- /dev/null +++ b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/igraph/IGraphDataSource.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.base.itc.igraph; + +import tools.refinery.viatra.runtime.matchers.util.IMemoryView; +import tools.refinery.viatra.runtime.matchers.util.IMultiset; + +import java.util.Set; + +/** + * The interface prescribes the set of operations that a graph data source must support. + *

Note that the old version of the interface is broken at version 1.6; + * MultiSets are now presented as Maps instead of Lists. + * + * @author Tamas Szabo + * + * @param + * the type of the nodes in the graph + */ +public interface IGraphDataSource { + + /** + * Attaches a new graph observer to this graph data source. Observers will be notified in the order they have been registered. + * + * @param observer the graph observer + */ + public void attachObserver(IGraphObserver observer); + + /** + * Attaches a new graph observer to this graph data source as the first one. + * In the notification order this observer will be the first one as long as another call to this method happens. + * + * @param observer the graph observer + * @since 1.6 + */ + public void attachAsFirstObserver(IGraphObserver observer); + + /** + * Detaches an already registered graph observer from this graph data source. + * + * @param observer the graph observer + */ + public void detachObserver(IGraphObserver observer); + + /** + * Returns the complete set of nodes in the graph data source. + * + * @return the set of all nodes + */ + public Set getAllNodes(); + + /** + * Returns the target nodes for the given source node. + * The returned data structure is an {@link IMultiset} because of potential parallel edges in the graph data source. + * + * The method must not return null. + * + * @param source the source node + * @return the multiset of target nodes + * @since 2.0 + */ + public IMemoryView getTargetNodes(V source); +} diff --git a/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/igraph/IGraphObserver.java b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/igraph/IGraphObserver.java new file mode 100644 index 00000000..5cb2d9fa --- /dev/null +++ b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/igraph/IGraphObserver.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.base.itc.igraph; + +/** + * Interface GraphObserver is used to observ the changes in a graph; edge and node insertion/deleteion. + * + * @author Tamas Szabo + * + */ +public interface IGraphObserver { + + /** + * Used to notify when an edge is inserted into the graph. + * + * @param source + * the source of the edge + * @param target + * the target of the edge + */ + public void edgeInserted(V source, V target); + + /** + * Used to notify when an edge is deleted from the graph. + * + * @param source + * the source of the edge + * @param target + * the target of the edge + */ + public void edgeDeleted(V source, V target); + + /** + * Used to notify when a node is inserted into the graph. + * + * @param n + * the node + */ + public void nodeInserted(V n); + + /** + * Used to notify when a node is deleted from the graph. + * + * @param n + * the node + */ + public void nodeDeleted(V n); +} diff --git a/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/igraph/ITcDataSource.java b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/igraph/ITcDataSource.java new file mode 100644 index 00000000..5924b723 --- /dev/null +++ b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/igraph/ITcDataSource.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.base.itc.igraph; + +import java.util.Set; + +import tools.refinery.viatra.runtime.base.itc.alg.misc.IGraphPathFinder; + +/** + * This interface defines those methods that a transitive reachability data source should provide. + * + * @author Tamas Szabo + * + * @param + * the type parameter of the node + */ +public interface ITcDataSource { + + /** + * Attach a transitive closure relation observer. + * + * @param to + * the observer object + */ + public void attachObserver(ITcObserver to); + + /** + * Detach a transitive closure relation observer. + * + * @param to + * the observer object + */ + public void detachObserver(ITcObserver to); + + /** + * Returns all nodes which are reachable from the source node. + * + * @param source + * the source node + * @return the set of target nodes + */ + public Set getAllReachableTargets(V source); + + /** + * Returns all nodes from which the target node is reachable. + * + * @param target + * the target node + * @return the set of source nodes + */ + public Set getAllReachableSources(V target); + + /** + * Returns true if the target node is reachable from the source node. + * + * @param source + * the source node + * @param target + * the target node + * @return true if target is reachable from source, false otherwise + */ + public boolean isReachable(V source, V target); + + /** + * The returned {@link IGraphPathFinder} can be used to retrieve paths between nodes using transitive reachability. + * + * @return a path finder for the graph. + */ + public IGraphPathFinder getPathFinder(); + + /** + * Call this method to properly dispose the data structures of a transitive closure algorithm. + */ + public void dispose(); +} diff --git a/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/igraph/ITcObserver.java b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/igraph/ITcObserver.java new file mode 100644 index 00000000..fded53f1 --- /dev/null +++ b/subprojects/viatra-runtime-base-itc/src/main/java/tools/refinery/viatra/runtime/base/itc/igraph/ITcObserver.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.base.itc.igraph; + +/** + * Interface ITcObserver is used to observe the changes in a transitive closure relation; tuple insertion/deletion. + * + * @author Szabo Tamas + * + */ +public interface ITcObserver { + + /** + * Used to notify when a tuple is inserted into the transitive closure relation. + * + * @param source + * the source of the tuple + * @param target + * the target of the tuple + */ + public void tupleInserted(V source, V target); + + /** + * Used to notify when a tuple is deleted from the transitive closure relation. + * + * @param source + * the source of the tuple + * @param target + * the target of the tuple + */ + public void tupleDeleted(V source, V target); +} diff --git a/subprojects/viatra-runtime-base/about.html b/subprojects/viatra-runtime-base/about.html new file mode 100644 index 00000000..d1d5593a --- /dev/null +++ b/subprojects/viatra-runtime-base/about.html @@ -0,0 +1,26 @@ + + + + +About + + + +

About This Content

+ +

March 18, 2019

+

License

+ +

The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise indicated below, the Content is provided to you under the terms and conditions of the +Eclipse Public License Version 2.0 ("EPL"). A copy of the EPL is available at http://www.eclipse.org/legal/epl-v20.html. +For purposes of the EPL, "Program" will mean the Content.

+ +

If you did not receive this Content directly from the Eclipse Foundation, the Content is being redistributed by another party ("Redistributor") and different terms and conditions may +apply to your use of any object code in the Content. Check the Redistributor's license that was provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise +indicated below, the terms and conditions of the EPL still apply to any source code in the Content and such source code may be obtained at http://www.eclipse.org.

+ + diff --git a/subprojects/viatra-runtime-base/build.gradle.kts b/subprojects/viatra-runtime-base/build.gradle.kts new file mode 100644 index 00000000..55c6acb8 --- /dev/null +++ b/subprojects/viatra-runtime-base/build.gradle.kts @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ + +plugins { + id("tools.refinery.gradle.java-library") +} + +dependencies { + api(project(":refinery-viatra-runtime-base-itc")) + api(project(":refinery-viatra-runtime-matchers")) + implementation(libs.eclipse) + implementation(libs.ecore) + implementation(libs.emf) + implementation(libs.osgi) + implementation(libs.slf4j.log4j) +} diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/ViatraBasePlugin.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/ViatraBasePlugin.java new file mode 100644 index 00000000..96b40825 --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/ViatraBasePlugin.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Gabor Bergmann, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.base; + +import org.eclipse.core.runtime.Plugin; +import tools.refinery.viatra.runtime.base.comprehension.WellbehavingDerivedFeatureRegistry; +import org.osgi.framework.BundleContext; + +public class ViatraBasePlugin extends Plugin { + + // The shared instance + private static ViatraBasePlugin plugin; + + public static final String PLUGIN_ID = "tools.refinery.viatra.runtime.base"; + public static final String WELLBEHAVING_DERIVED_FEATURE_EXTENSION_POINT_ID = "tools.refinery.viatra.runtime.base.wellbehaving.derived.features"; + + @Override + public void start(BundleContext context) throws Exception { + super.start(context); + plugin = this; + WellbehavingDerivedFeatureRegistry.initRegistry(); + } + + @Override + public void stop(BundleContext context) throws Exception { + plugin = null; + super.stop(context); + } + + /** + * Returns the shared instance + * + * @return the shared instance + */ + public static ViatraBasePlugin getDefault() { + return plugin; + } + +} diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/BaseIndexOptions.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/BaseIndexOptions.java new file mode 100644 index 00000000..92663400 --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/BaseIndexOptions.java @@ -0,0 +1,395 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Abel Hegedus, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.base.api; + +import java.util.Objects; + +import tools.refinery.viatra.runtime.base.api.filters.IBaseIndexFeatureFilter; +import tools.refinery.viatra.runtime.base.api.filters.IBaseIndexObjectFilter; +import tools.refinery.viatra.runtime.base.api.filters.IBaseIndexResourceFilter; +import tools.refinery.viatra.runtime.base.api.profiler.ProfilerMode; + +/** + * The base index options indicate how the indices are built. + * + *

+ * One of the options is to build indices in wildcard mode, meaning that all EClasses, EDataTypes, EReferences + * and EAttributes are indexed. This is convenient, but comes at a high memory cost. To save memory, one can disable + * wildcard mode and manually register those EClasses, EDataTypes, EReferences and EAttributes that should be + * indexed. + *

+ * + *

+ * Another choice is whether to build indices in dynamic EMF mode, meaning that types are identified by the + * String IDs that are ultimately derived from the nsURI of the EPackage. Multiple types with the same ID are treated as + * the same. This is useful if dynamic EMF is used, where there can be multiple copies (instantiations) of the same + * EPackage, representing essentially the same metamodel. If one disables dynamic EMF mode, an error is logged + * if duplicate EPackages with the same nsURI are encountered. + *

+ * + * @author Abel Hegedus + * @noimplement This class is not intended to be subclasses outside of VIATRA runtime + * + */ +public class BaseIndexOptions { + + /** + * + * By default, base indices will be constructed with wildcard mode set as false. + */ + protected static final IndexingLevel WILDCARD_MODE_DEFAULT = IndexingLevel.NONE; + /** + * + * By default, base indices will be constructed with only well-behaving features traversed. + */ + private static final boolean TRAVERSE_ONLY_WELLBEHAVING_DERIVED_FEATURES_DEFAULT = true; + /** + * + * By default, base indices will be constructed with dynamic EMF mode set as false. + */ + protected static final boolean DYNAMIC_EMF_MODE_DEFAULT = false; + + /** + * + * By default, the scope will make the assumption that it is free from dangling edges. + * @since 1.6 + */ + protected static final boolean DANGLING_FREE_ASSUMPTION_DEFAULT = true; + + /** + * By default, duplicate notifications are only logged. + * + * @since 1.6 + */ + protected static final boolean STRICT_NOTIFICATION_MODE_DEFAULT = true; + + /** + * @since 2.3 + */ + protected static final ProfilerMode INDEX_PROFILER_MODE_DEFAULT = ProfilerMode.OFF; + + /** + * @since 1.6 + */ + protected boolean danglingFreeAssumption = DANGLING_FREE_ASSUMPTION_DEFAULT; + protected boolean dynamicEMFMode = DYNAMIC_EMF_MODE_DEFAULT; + protected boolean traverseOnlyWellBehavingDerivedFeatures = TRAVERSE_ONLY_WELLBEHAVING_DERIVED_FEATURES_DEFAULT; + protected IndexingLevel wildcardMode = WILDCARD_MODE_DEFAULT; + protected IBaseIndexObjectFilter notifierFilterConfiguration; + protected IBaseIndexResourceFilter resourceFilterConfiguration; + /** + * @since 1.5 + */ + protected IBaseIndexFeatureFilter featureFilterConfiguration; + + /** + * If strict notification mode is turned on, errors related to inconsistent notifications, e.g. duplicate deletions + * cause the entire Base index to be considered invalid, e.g. the query engine on top of the index should become + * tainted. + * + * @since 1.6 + */ + protected boolean strictNotificationMode = STRICT_NOTIFICATION_MODE_DEFAULT; + + /** + * Returns whether base index profiling should be turned on. + * + * @since 2.3 + */ + protected ProfilerMode indexerProfilerMode = INDEX_PROFILER_MODE_DEFAULT; + + /** + * Creates a base index options with the default values. + */ + public BaseIndexOptions() { + } + + /** + * Creates a base index options using the provided values for dynamic EMF mode and wildcard mode. + * @since 1.4 + */ + public BaseIndexOptions(boolean dynamicEMFMode, IndexingLevel wildcardMode) { + this.dynamicEMFMode = dynamicEMFMode; + this.wildcardMode = wildcardMode; + } + + /** + * + * @param dynamicEMFMode + * @since 0.9 + */ + public BaseIndexOptions withDynamicEMFMode(boolean dynamicEMFMode) { + BaseIndexOptions result = copy(); + result.dynamicEMFMode = dynamicEMFMode; + return result; + } + + /** + * Sets the dangling edge handling property of the index option. If not set explicitly, it is considered as `true`. + * @param danglingFreeAssumption if true, + * the base index will assume that there are no dangling references + * (pointing out of scope or to proxies) + * @since 1.6 + */ + public BaseIndexOptions withDanglingFreeAssumption(boolean danglingFreeAssumption) { + BaseIndexOptions result = copy(); + result.danglingFreeAssumption = danglingFreeAssumption; + return result; + } + + /** + * Adds an object-level filter to the indexer configuration. Warning - object-level indexing can increase indexing time + * noticeably. If possibly, use {@link #withResourceFilterConfiguration(IBaseIndexResourceFilter)} instead. + * + * @param filter + * @since 0.9 + */ + public BaseIndexOptions withObjectFilterConfiguration(IBaseIndexObjectFilter filter) { + BaseIndexOptions result = copy(); + result.notifierFilterConfiguration = filter; + return result; + } + + /** + * @return the selected object filter configuration, or null if not set + */ + public IBaseIndexObjectFilter getObjectFilterConfiguration() { + return notifierFilterConfiguration; + } + + /** + * Returns a copy of the configuration with a specified resource filter + * + * @param filter + * @since 0.9 + */ + public BaseIndexOptions withResourceFilterConfiguration(IBaseIndexResourceFilter filter) { + BaseIndexOptions result = copy(); + result.resourceFilterConfiguration = filter; + return result; + } + + /** + * @return the selected resource filter, or null if not set + */ + public IBaseIndexResourceFilter getResourceFilterConfiguration() { + return resourceFilterConfiguration; + } + + + /** + * Returns a copy of the configuration with a specified feature filter + * + * @param filter + * @since 1.5 + */ + public BaseIndexOptions withFeatureFilterConfiguration(IBaseIndexFeatureFilter filter) { + BaseIndexOptions result = copy(); + result.featureFilterConfiguration = filter; + return result; + } + + /** + * @return the selected feature filter, or null if not set + * @since 1.5 + */ + public IBaseIndexFeatureFilter getFeatureFilterConfiguration() { + return featureFilterConfiguration; + } + + + /** + * @return whether the base index option has dynamic EMF mode set + */ + public boolean isDynamicEMFMode() { + return dynamicEMFMode; + } + + /** + * @return whether the base index makes the assumption that there can be no dangling edges + * @since 1.6 + */ + public boolean isDanglingFreeAssumption() { + return danglingFreeAssumption; + } + + /** + * @return whether the base index option has traverse only well-behaving derived features set + */ + public boolean isTraverseOnlyWellBehavingDerivedFeatures() { + return traverseOnlyWellBehavingDerivedFeatures; + } + + /** + * + * @param wildcardMode + * @since 1.4 + */ + public BaseIndexOptions withWildcardLevel(IndexingLevel wildcardLevel) { + BaseIndexOptions result = copy(); + result.wildcardMode = wildcardLevel; + return result; + } + + /** + * @since 1.6 + */ + public BaseIndexOptions withStrictNotificationMode(boolean strictNotificationMode) { + BaseIndexOptions result = copy(); + result.strictNotificationMode = strictNotificationMode; + return result; + } + + /** + * @since 2.3 + */ + public BaseIndexOptions withIndexProfilerMode(ProfilerMode indexerProfilerMode) { + BaseIndexOptions result = copy(); + result.indexerProfilerMode = indexerProfilerMode; + return result; + } + + /** + * @return whether the base index option has wildcard mode set + */ + public boolean isWildcardMode() { + return wildcardMode == IndexingLevel.FULL; + } + + /** + * @return the wildcardMode level + * @since 1.4 + */ + public IndexingLevel getWildcardLevel() { + return wildcardMode; + } + + /** + * If strict notification mode is turned on, errors related to inconsistent notifications, e.g. duplicate deletions + * cause the entire Base index to be considered invalid, e.g. the query engine on top of the index should become + * tainted. + * + * @since 1.6 + */ + public boolean isStrictNotificationMode() { + return strictNotificationMode; + } + + /** + * Returns whether base indexer profiling is enabled. The profiling causes some slowdown, but provides information + * about how much time the base indexer takes for initialization and updates. + * + * @since 2.3 + */ + public ProfilerMode getIndexerProfilerMode() { + return indexerProfilerMode; + } + + /** + * Creates an independent copy of itself. The values of each option will be the same as this options. This method is + * used when a provided option must be copied to avoid external option changes afterward. + * + * @return the copy of this options + */ + public BaseIndexOptions copy() { + BaseIndexOptions baseIndexOptions = new BaseIndexOptions(this.dynamicEMFMode, this.wildcardMode); + baseIndexOptions.danglingFreeAssumption = this.danglingFreeAssumption; + baseIndexOptions.traverseOnlyWellBehavingDerivedFeatures = this.traverseOnlyWellBehavingDerivedFeatures; + baseIndexOptions.notifierFilterConfiguration = this.notifierFilterConfiguration; + baseIndexOptions.resourceFilterConfiguration = this.resourceFilterConfiguration; + baseIndexOptions.featureFilterConfiguration = this.featureFilterConfiguration; + baseIndexOptions.strictNotificationMode = this.strictNotificationMode; + baseIndexOptions.indexerProfilerMode = this.indexerProfilerMode; + return baseIndexOptions; + } + + @Override + public int hashCode() { + return Objects.hash(dynamicEMFMode, notifierFilterConfiguration, resourceFilterConfiguration, + featureFilterConfiguration, traverseOnlyWellBehavingDerivedFeatures, wildcardMode, strictNotificationMode, + danglingFreeAssumption, indexerProfilerMode); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof BaseIndexOptions)) + return false; + BaseIndexOptions other = (BaseIndexOptions) obj; + if (dynamicEMFMode != other.dynamicEMFMode) + return false; + if (danglingFreeAssumption != other.danglingFreeAssumption) + return false; + if (notifierFilterConfiguration == null) { + if (other.notifierFilterConfiguration != null) + return false; + } else if (!notifierFilterConfiguration + .equals(other.notifierFilterConfiguration)) + return false; + if (resourceFilterConfiguration == null) { + if (other.resourceFilterConfiguration != null) + return false; + } else if (!resourceFilterConfiguration + .equals(other.resourceFilterConfiguration)){ + return false; + } + + if (featureFilterConfiguration == null) { + if (other.featureFilterConfiguration != null) + return false; + } else if (!featureFilterConfiguration + .equals(other.featureFilterConfiguration)){ + return false; + } + + if (traverseOnlyWellBehavingDerivedFeatures != other.traverseOnlyWellBehavingDerivedFeatures) + return false; + if (wildcardMode != other.wildcardMode) + return false; + if (strictNotificationMode != other.strictNotificationMode) { + return false; + } + if (indexerProfilerMode != other.indexerProfilerMode) { + return false; + } + return true; + } + + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + appendModifier(sb, dynamicEMFMode, DYNAMIC_EMF_MODE_DEFAULT, "dynamicEMF"); + appendModifier(sb, wildcardMode, WILDCARD_MODE_DEFAULT, "wildcard"); + appendModifier(sb, danglingFreeAssumption, DANGLING_FREE_ASSUMPTION_DEFAULT, "danglingFreeAssumption"); + appendModifier(sb, traverseOnlyWellBehavingDerivedFeatures, TRAVERSE_ONLY_WELLBEHAVING_DERIVED_FEATURES_DEFAULT, "wellBehavingOnly"); + appendModifier(sb, strictNotificationMode, STRICT_NOTIFICATION_MODE_DEFAULT, "strictNotificationMode"); + appendModifier(sb, indexerProfilerMode, INDEX_PROFILER_MODE_DEFAULT, "indexerProfilerMode"); + appendModifier(sb, notifierFilterConfiguration, null, "notifierFilter="); + appendModifier(sb, resourceFilterConfiguration, null, "resourceFilter="); + appendModifier(sb, featureFilterConfiguration, null, "featureFilterConfiguration="); + final String result = sb.toString(); + return result.isEmpty() ? "defaults" : result; + } + + private static void appendModifier(StringBuilder sb, Object actualValue, Object expectedValue, String switchName) { + if (Objects.equals(expectedValue, actualValue)) { + // silent + } else { + sb.append(Boolean.FALSE.equals(actualValue) ? '-' : '+'); + sb.append(switchName); + if (! (actualValue instanceof Boolean)) + sb.append(actualValue); + } + } + +} diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/DataTypeListener.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/DataTypeListener.java new file mode 100644 index 00000000..3d30df5e --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/DataTypeListener.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Gabor Bergmann, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.base.api; + +import org.eclipse.emf.ecore.EDataType; + +/** + * Interface for observing insertion and deletion of instances of data types. + * + * @author Tamas Szabo + * + */ +public interface DataTypeListener { + + /** + * Called when an instance of the given type is inserted. + * + * @param type + * the {@link EDataType} + * @param instance + * the instance of the data type + * @param firstOccurrence + * true if this value was not previously present in the model + */ + public void dataTypeInstanceInserted(EDataType type, Object instance, boolean firstOccurrence); + + /** + * Called when an instance of the given type is deleted. + * + * @param type + * the {@link EDataType} + * @param instance + * the instance of the data type + * @param lastOccurrence + * true if this value is no longer present in the model + */ + public void dataTypeInstanceDeleted(EDataType type, Object instance, boolean lastOccurrence); +} diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/EMFBaseIndexChangeListener.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/EMFBaseIndexChangeListener.java new file mode 100644 index 00000000..5a2486f9 --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/EMFBaseIndexChangeListener.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Abel Hegedus, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.base.api; + +/** + * Listener interface for change notifications from the VIATRA Base index. + * + * @author Abel Hegedus + * + */ +public interface EMFBaseIndexChangeListener { + + /** + * NOTE: it is possible that this method is called only ONCE! Consider returning a constant value that is set in the constructor. + * + * @return true, if the listener should be notified only after index changes, false if notification is needed after each model change + */ + public boolean onlyOnIndexChange(); + + /** + * Called after a model change is handled by the VIATRA Base index and if indexChanged == onlyOnIndexChange(). + * + * @param indexChanged true, if the model change also affected the contents of the base index + */ + public void notifyChanged(boolean indexChanged); + +} diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/FeatureListener.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/FeatureListener.java new file mode 100644 index 00000000..fa2d679e --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/FeatureListener.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Gabor Bergmann, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.base.api; + +import org.eclipse.emf.ecore.EAttribute; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.EStructuralFeature; + +/** + * Interface for observing insertion and deletion of structural feature values ("settings"). (Works both for + * single-valued and many-valued features.) + * + * @author Tamas Szabo + * + */ +public interface FeatureListener { + + /** + * Called when the given value is inserted into the given feature of the given host EObject. + * + * @param host + * the host (holder) of the feature + * @param feature + * the {@link EAttribute} or {@link EReference} instance + * @param value + * the target of the feature + */ + public void featureInserted(EObject host, EStructuralFeature feature, Object value); + + /** + * Called when the given value is removed from the given feature of the given host EObject. + * + * @param host + * the host (holder) of the feature + * @param feature + * the {@link EAttribute} or {@link EReference} instance + * @param value + * the target of the feature + */ + public void featureDeleted(EObject host, EStructuralFeature feature, Object value); +} diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IEClassifierProcessor.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IEClassifierProcessor.java new file mode 100644 index 00000000..aaa98918 --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IEClassifierProcessor.java @@ -0,0 +1,25 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Abel Hegedus, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.base.api; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EDataType; +import org.eclipse.emf.ecore.EObject; + +/** + * @author Abel Hegedus + * + */ +public interface IEClassifierProcessor { + + void process(ClassType type, InstanceType instance); + + public interface IEClassProcessor extends IEClassifierProcessor{} + public interface IEDataTypeProcessor extends IEClassifierProcessor{} +} diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IEMFIndexingErrorListener.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IEMFIndexingErrorListener.java new file mode 100644 index 00000000..1dc3291b --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IEMFIndexingErrorListener.java @@ -0,0 +1,22 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.base.api; + +/** + * + * This interface contains callbacks for various internal errors from the {@link NavigationHelper base index}. + * + * @author Zoltan Ujhelyi + * + */ +public interface IEMFIndexingErrorListener { + + void error(String description, Throwable t); + void fatal(String description, Throwable t); +} diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IQueryResultSetter.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IQueryResultSetter.java new file mode 100644 index 00000000..717bad4b --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IQueryResultSetter.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Abel Hegedus, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.base.api; + +/** + * Setter interface for query result multimaps that allow modifications of the model through the multimap. + * + *

+ * The model modifications should ensure that the multimap changes exactly as required (i.e. a put results in only one + * new key-value pair and remove results in only one removed pair). + * + *

+ * The input parameters of both put and remove can be validated by implementing the {@link #validate(Object, Object)} + * method. + * + * @author Abel Hegedus + * + * @param + * @param + */ +public interface IQueryResultSetter { + /** + * Modify the underlying model of the query in order to have the given key-value pair as a new result of the query. + * + * @param key + * the key for which a new value is added to the query results + * @param value + * the new value that should be added to the query results for the given key + * @return true, if the query result changed + */ + boolean put(KeyType key, ValueType value); + + /** + * Modify the underlying model of the query in order to remove the given key-value pair from the results of the + * query. + * + * @param key + * the key for which the value is removed from the query results + * @param value + * the value that should be removed from the query results for the given key + * @return true, if the query result changed + */ + boolean remove(KeyType key, ValueType value); + + /** + * Validates a given key-value pair for the query result. The validation has to ensure that (1) if the pair does not + * exist in the result, it can be added safely (2) if the pair already exists in the result, it can be removed + * safely + * + * @param key + * the key of the pair that is validated + * @param value + * the value of the pair that is validated + * @return true, if the pair does not exists but can be added or the pair exists and can be removed + */ + boolean validate(KeyType key, ValueType value); +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IQueryResultUpdateListener.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IQueryResultUpdateListener.java new file mode 100644 index 00000000..5addfd78 --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IQueryResultUpdateListener.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Abel Hegedus, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.base.api; + +/** + * Listener interface for receiving notification from {@link QueryResultMultimap} + * + * @author Abel Hegedus + * + * @param + * @param + */ +public interface IQueryResultUpdateListener { + /** + * This method is called by the query result multimap when a new key-value pair is put into the multimap + * + *

+ * Only invoked if the contents of the multimap changed! + * + * @param key + * the key of the newly inserted pair + * @param value + * the value of the newly inserted pair + */ + void notifyPut(KeyType key, ValueType value); + + /** + * This method is called by the query result multimap when key-value pair is removed from the multimap + * + *

+ * Only invoked if the contents of the multimap changed! + * + * @param key + * the key of the removed pair + * @param value + * the value of the removed pair + */ + void notifyRemove(KeyType key, ValueType value); +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IStructuralFeatureInstanceProcessor.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IStructuralFeatureInstanceProcessor.java new file mode 100644 index 00000000..208ad761 --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IStructuralFeatureInstanceProcessor.java @@ -0,0 +1,19 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.base.api; + +import org.eclipse.emf.ecore.EObject; + +/** + * @author Gabor Bergmann + * @since 1.7 + */ +public interface IStructuralFeatureInstanceProcessor { + void process(EObject source, Object target); +} diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IndexingLevel.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IndexingLevel.java new file mode 100644 index 00000000..df5c59f5 --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IndexingLevel.java @@ -0,0 +1,113 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Grill Balázs, IncQueryLabs + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.base.api; + +import tools.refinery.viatra.runtime.matchers.context.IndexingService; + +import java.util.Set; + +/** + * The values of this enum denotes the level of indexing the base indexer is capable of. + * + * @author Grill Balázs + * @since 1.4 + * + */ +public enum IndexingLevel { + + /** + * No indexing is performed + */ + NONE, + + /** + * Only cardinality information is stored. This indexing level makes possible to calculate + * results of {@link NavigationHelper#countAllInstances(org.eclipse.emf.ecore.EClass)}, {@link NavigationHelper#countFeatures(org.eclipse.emf.ecore.EStructuralFeature)} + * and {@link NavigationHelper#countDataTypeInstances(org.eclipse.emf.ecore.EDataType)} with minimal memory footprint. + */ + STATISTICS, + + /** + * Notifications are dispatched about the changes + */ + NOTIFICATIONS, + + /** + * Cardinality information is stored and live notifications are dispatched + */ + BOTH, + + /** + * Full indexing is performed, set of instances is available + */ + FULL + + ; + + private static final IndexingLevel[][] mergeTable = { + /* NONE STATISTICS NOTIFICATIONS BOTH FULL*/ + /* NONE */{ NONE, STATISTICS, NOTIFICATIONS, BOTH, FULL}, + /* STATISTICS */{ STATISTICS, STATISTICS, BOTH, BOTH, FULL}, + /* NOTIFICATIONS */{ NOTIFICATIONS, BOTH, NOTIFICATIONS, BOTH, FULL}, + /* BOTH */{ BOTH, BOTH, BOTH, BOTH, FULL}, + /* FULL */{ FULL, FULL, FULL, FULL, FULL} + }; + + public static IndexingLevel toLevel(IndexingService service){ + switch(service){ + case INSTANCES: + return IndexingLevel.FULL; + case NOTIFICATIONS: + return IndexingLevel.NOTIFICATIONS; + case STATISTICS: + return IndexingLevel.STATISTICS; + default: + return IndexingLevel.NONE; + } + } + + public static IndexingLevel toLevel(Set services){ + IndexingLevel result = NONE; + for(IndexingService service : services){ + result = result.merge(toLevel(service)); + } + return result; + } + + /** + * Merge this level with the given other level, The resulting indexing level will provide the + * functionality which conforms to both given levels. + */ + public IndexingLevel merge(IndexingLevel other){ + if (other == null) return this; + return mergeTable[this.ordinal()][other.ordinal()]; + } + + /** + * Tells whether the indexer shall perform separate statistics calculation for this level + */ + public boolean hasStatistics() { + return this == IndexingLevel.BOTH || this == IndexingLevel.STATISTICS || this == IndexingLevel.FULL; + } + + /** + * Tells whether the indexer shall perform instance indexing + */ + public boolean hasInstances(){ + return this == IndexingLevel.FULL; + } + + /** + * Returns whether the current indexing level includes all features from the parameter level + * @since 1.5 + */ + public boolean providesLevel(IndexingLevel level) { + return this.merge(level) == this; + } +} diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/InstanceListener.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/InstanceListener.java new file mode 100644 index 00000000..6339545d --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/InstanceListener.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Gabor Bergmann, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.base.api; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EObject; + +/** + * Interface for observing insertion / deletion of instances of EClass. + * + * @author Tamas Szabo + * + */ +public interface InstanceListener { + + /** + * Called when the given instance was added to the model. + * + * @param clazz + * an EClass registered for this listener, for which a new instance (possibly an instance of a subclass) was inserted into the model + * @param instance + * an EObject instance that was inserted into the model + */ + public void instanceInserted(EClass clazz, EObject instance); + + /** + * Called when the given instance was removed from the model. + * + * @param clazz + * an EClass registered for this listener, for which an instance (possibly an instance of a subclass) was removed from the model + * @param instance + * an EObject instance that was removed from the model + */ + public void instanceDeleted(EClass clazz, EObject instance); +} diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/LightweightEObjectObserver.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/LightweightEObjectObserver.java new file mode 100644 index 00000000..f0245b5d --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/LightweightEObjectObserver.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Abel Hegedus, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.base.api; + +import org.eclipse.emf.common.notify.Notification; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EStructuralFeature; + + +/** + * Listener interface for lightweight observation on EObject feature value changes. + * + * @author Abel Hegedus + * + */ +public interface LightweightEObjectObserver { + + /** + * + * @param host + * @param feature + * @param notification + */ + void notifyFeatureChanged(EObject host, EStructuralFeature feature, Notification notification); + +} diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/LightweightEObjectObserverAdapter.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/LightweightEObjectObserverAdapter.java new file mode 100644 index 00000000..bcdb8ff4 --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/LightweightEObjectObserverAdapter.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Abel Hegedus, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.base.api; + +import static tools.refinery.viatra.runtime.matchers.util.Preconditions.checkArgument; + +import java.util.Collection; +import java.util.HashSet; + +import org.eclipse.emf.common.notify.Notification; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EStructuralFeature; + +/** + * Adapter class for lightweight observer which filters feature updates to a selected set of features. + * + * @author Abel Hegedus + * + */ +public abstract class LightweightEObjectObserverAdapter implements LightweightEObjectObserver { + + private Collection observedFeatures; + + /** + * Creates a new adapter with the given set of observed features. + */ + public LightweightEObjectObserverAdapter(Collection observedFeatures) { + checkArgument(observedFeatures != null, "List of observed features must not be null!"); + this.observedFeatures = new HashSet<>(observedFeatures); + } + + public void observeAdditionalFeature(EStructuralFeature observedFeature) { + checkArgument(observedFeature != null, "Cannot observe null feature!"); + this.observedFeatures.add(observedFeature); + } + + public void observeAdditionalFeatures(Collection observedFeatures) { + checkArgument(observedFeatures != null, "List of additional observed features must not be null!"); + this.observedFeatures.addAll(observedFeatures); + } + + public void removeObservedFeature(EStructuralFeature observedFeature) { + checkArgument(observedFeature != null, "Cannot remove null observed feature!"); + this.observedFeatures.remove(observedFeature); + } + + public void removeObservedFeatures(Collection observedFeatures) { + checkArgument(observedFeatures != null, "List of observed features to remove must not be null!"); + this.observedFeatures.removeAll(observedFeatures); + } + + @Override + public void notifyFeatureChanged(EObject host, EStructuralFeature feature, Notification notification) { + if(this.observedFeatures.contains(feature)) { + observedFeatureUpdate(host, feature, notification); + } + } + + /** + * This method is called when the feature that changed is among the observed features of the adapter. + * + * @param host + * @param feature + * @param notification + */ + public abstract void observedFeatureUpdate(EObject host, EStructuralFeature feature, Notification notification); + +} diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/NavigationHelper.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/NavigationHelper.java new file mode 100644 index 00000000..6a9f704b --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/NavigationHelper.java @@ -0,0 +1,885 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Gabor Bergmann, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.base.api; + +import org.eclipse.emf.common.notify.Notifier; +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.common.util.Enumerator; +import org.eclipse.emf.ecore.*; +import org.eclipse.emf.ecore.EStructuralFeature.Setting; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.ResourceSet; +import tools.refinery.viatra.runtime.base.api.IEClassifierProcessor.IEClassProcessor; +import tools.refinery.viatra.runtime.base.api.IEClassifierProcessor.IEDataTypeProcessor; +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; + +import java.lang.reflect.InvocationTargetException; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.Set; +import java.util.concurrent.Callable; + +/** + * + * Using an index of the EMF model, this interface exposes useful query functionality, such as: + *

    + *
  • + * Getting all the (direct or descendant) instances of a given {@link EClass} + *
  • + * Inverse navigation along arbitrary {@link EReference} instances (heterogenous paths too) + *
  • + * Finding model elements by attribute value (i.e. inverse navigation along {@link EAttribute}) + *
  • + * Querying instances of given data types, or structural features. + *
+ * As queries are served from an index, results are always instantaneous. + * + *

+ * Such indices will be built on an EMF model rooted at an {@link EObject}, {@link Resource} or {@link ResourceSet}. + * The boundaries of the model are defined by the containment (sub)tree. + * The indices will be maintained incrementally on changes to the model; these updates can also be + * observed by registering listeners. + *

+ * + *

+ * One of the options is to build indices in wildcard mode, meaning that all EClasses, EDataTypes, EReferences + * and EAttributes are indexed. This is convenient, but comes at a high memory cost. To save memory, one can disable + * wildcard mode and manually register those EClasses, EDataTypes, EReferences and EAttributes that should be + * indexed. + *

+ * + *

+ * Another choice is whether to build indices in dynamic EMF mode, meaning that types are identified by the String IDs + * that are ultimately derived from the nsURI of the EPackage. Multiple types with the same ID are treated as the same. + * This is useful if dynamic EMF is used, where there can be multiple copies (instantiations) of the same EPackage, + * representing essentially the same metamodel. If one disables dynamic EMF mode, an error is logged if + * duplicate EPackages with the same nsURI are encountered. + *

+ * + *

+ * Note that none of the defined query methods return null upon empty result sets. All query methods return either a copy of + * the result sets (where {@link Setting} is instantiated) or an unmodifiable collection of the result view. + * + *

+ * Instantiate using {@link ViatraBaseFactory} + * + * @author Tamas Szabo + * @noimplement This interface is not intended to be implemented by clients. + * + */ +public interface NavigationHelper { + + /** + * Indicates whether indexing is performed in wildcard mode, where every aspect of the EMF model is + * automatically indexed. + * + * @return true if everything is indexed, false if manual registration of interesting EClassifiers and + * EStructuralFeatures is required. + */ + public boolean isInWildcardMode(); + + /** + * Indicates whether indexing is performed in wildcard mode for a selected indexing level + * + * @return true if everything is indexed, false if manual registration of interesting EClassifiers and + * EStructuralFeatures is required. + * @since 1.5 + */ + public boolean isInWildcardMode(IndexingLevel level); + + /** + * Returns the current {@link IndexingLevel} applied to all model elements. For specific types it is possible to request a higher indexing levels, but cannot be lowered. + * @return the current level of index specified + * @since 1.4 + */ + public IndexingLevel getWildcardLevel(); + + /** + * Starts wildcard indexing at the given level. After this call, no registration is required for this {@link IndexingLevel}. + * a previously set wildcard level cannot be lowered, only extended. + * + * @since 1.4 + */ + public void setWildcardLevel(IndexingLevel level); + + /** + * Indicates whether indexing is performed in dynamic EMF mode, i.e. EPackage nsURI collisions are + * tolerated and EPackages with the same URI are automatically considered as equal. + * + * @return true if multiple EPackages with the same nsURI are treated as the same, + * false if an error is logged instead in this case. + */ + public boolean isInDynamicEMFMode(); + + /** + * For a given attribute value value, find each {@link EAttribute} and host {@link EObject} + * such that this attribute of the the host object takes the given value. The method will + * return a set of {@link Setting}s, one for each such host object - EAttribute - value triplet. + * + *

+ * Precondition: Unset / null attribute values are not indexed, so value!=null + * + *

+ * Precondition: Will only find those EAttributes that have already been registered using + * {@link #registerEStructuralFeatures(Set)}, unless running in wildcard mode (see + * {@link #isInWildcardMode()}). + * + * @param value + * the value of the attribute + * @return a set of {@link Setting}s, one for each EObject and EAttribute that have the given value + * @see #findByAttributeValue(Object) + */ + public Set findByAttributeValue(Object value); + + /** + * For given attributes and an attribute value value, find each host {@link EObject} + * such that any of these attributes of the the host object takes the given value. The method will + * return a set of {@link Setting}s, one for each such host object - EAttribute - value triplet. + * + *

+ * Precondition: Unset / null attribute values are not indexed, so value!=null + * + *

+ * Precondition: Will only find those EAttributes that have already been registered using + * {@link #registerEStructuralFeatures(Set)}, unless running in wildcard mode (see + * {@link #isInWildcardMode()}). + * + * @param value + * the value of the attribute + * @param attributes + * the collection of attributes that should take the given value + * @return a set of {@link Setting}s, one for each EObject and attribute that have the given value + */ + public Set findByAttributeValue(Object value, Collection attributes); + + /** + * Find all {@link EObject}s for which the given attribute takes the given value. + * + *

+ * Precondition: Unset / null attribute values are not indexed, so value!=null + * + *

+ * Precondition: Results will be returned only if either (a) the EAttribute has already been + * registered using {@link #registerEStructuralFeatures(Set)}, or (b) running in wildcard mode (see + * {@link #isInWildcardMode()}). + * + * @param value + * the value of the attribute + * @param attribute + * the EAttribute that should take the given value + * @return the set of {@link EObject}s for which the given attribute has the given value + */ + public Set findByAttributeValue(Object value, EAttribute attribute); + + /** + * Returns the set of instances for the given {@link EDataType} that can be found in the model. + * + *

+ * Precondition: Results will be returned only if either (a) the EDataType has already been + * registered using {@link #registerEDataTypes(Set)}, or (b) running in wildcard mode (see + * {@link #isInWildcardMode()}). + * + * @param type + * the data type + * @return the set of all attribute values found in the model that are of the given data type + */ + public Set getDataTypeInstances(EDataType type); + + /** + * Returns whether an object is an instance for the given {@link EDataType} that can be found in the current scope. + *

+ * Precondition: Result will be true only if either (a) the EDataType has already been registered + * using {@link #registerEDataTypes(Set)}, or (b) running in wildcard mode (see + * {@link #isInWildcardMode()}). + * + * @param value a non-null value to decide whether it is available as an EDataType instance + * @param type a non-null EDataType + * @return true, if a corresponding instance was found + * @since 1.7 + */ + public boolean isInstanceOfDatatype(Object value, EDataType type); + + /** + * Find all {@link EObject}s that are the target of the EReference reference from the given + * source {@link EObject}. + * + *

+ * Unset / null-valued references are not indexed, and will not be included in the results. + * + *

+ * Precondition: Results will be returned only if either (a) the reference has already been + * registered using {@link #registerEStructuralFeatures(Set)}, or (b) running in wildcard mode (see + * {@link #isInWildcardMode()}). + * + * @param source the host object + * @param reference an EReference of the host object + * @return the set of {@link EObject}s that the given reference points to, from the given source object + */ + public Set getReferenceValues(EObject source, EReference reference); + + /** + * Find all {@link Object}s that are the target of the EStructuralFeature feature from the given + * source {@link EObject}. + * + *

+ * Unset / null-valued features are not indexed, and will not be included in the results. + * + *

+ * Precondition: Results will be returned only if either (a) the feature has already been + * registered, or (b) running in wildcard mode (see + * {@link #isInWildcardMode()}). + * + * @param source the host object + * @param feature an EStructuralFeature of the host object + * @return the set of values that the given feature takes at the given source object + * + * @see #getReferenceValues(EObject, EReference) + */ + public Set getFeatureTargets(EObject source, EStructuralFeature feature); + + /** + * Decides whether the given non-null source and target objects are connected via a specific, indexed EStructuralFeature instance. + * + *

+ * Unset / null-valued features are not indexed, and will not be included in the results. + * + *

+ * Precondition: Result will be true only if either (a) the feature has already been + * registered, or (b) running in wildcard mode (see + * {@link #isInWildcardMode()}). + * @since 1.7 + */ + public boolean isFeatureInstance(EObject source, Object target, EStructuralFeature feature); + + /** + * For a given {@link EObject} target, find each {@link EReference} and source {@link EObject} + * such that this reference (list) of the the host object points to the given target object. The method will + * return a set of {@link Setting}s, one for each such source object - EReference - target triplet. + * + *

+ * Precondition: Unset / null reference values are not indexed, so target!=null + * + *

+ * Precondition: Results will be returned only for those references that have already been + * registered using {@link #registerEStructuralFeatures(Set)}, or all references if running in + * wildcard mode (see {@link #isInWildcardMode()}). + * + * @param target + * the EObject pointed to by the references + * @return a set of {@link Setting}s, one for each source EObject and reference that point to the given target + */ + public Set getInverseReferences(EObject target); + + /** + * For given references and an {@link EObject} target, find each source {@link EObject} + * such that any of these references of the the source object points to the given target object. The method will + * return a set of {@link Setting}s, one for each such source object - EReference - target triplet. + * + *

+ * Precondition: Unset / null reference values are not indexed, so target!=null + * + *

+ * Precondition: Will only find those EReferences that have already been registered using + * {@link #registerEStructuralFeatures(Set)}, unless running in wildcard mode (see + * {@link #isInWildcardMode()}). + * + * @param target + * the EObject pointed to by the references + * @param references a set of EReferences pointing to the target + * @return a set of {@link Setting}s, one for each source EObject and reference that point to the given target + */ + public Set getInverseReferences(EObject target, Collection references); + + /** + * Find all source {@link EObject}s for which the given reference points to the given target object. + * + *

+ * Precondition: Unset / null reference values are not indexed, so target!=null + * + *

+ * Precondition: Results will be returned only if either (a) the reference has already been + * registered using {@link #registerEStructuralFeatures(Set)}, or (b) running in wildcard mode (see + * {@link #isInWildcardMode()}). + * + * @param target + * the EObject pointed to by the references + * @param reference + * an EReference pointing to the target + * @return the collection of {@link EObject}s for which the given reference points to the given target object + */ + public Set getInverseReferences(EObject target, EReference reference); + + /** + * Get the direct {@link EObject} instances of the given {@link EClass}. Instances of subclasses will be excluded. + * + *

+ * Precondition: Results will be returned only if either (a) the EClass (or any superclass) has + * already been registered using {@link #registerEClasses(Set)}, or (b) running in wildcard mode (see + * {@link #isInWildcardMode()}). + * + * @param clazz + * an EClass + * @return the collection of {@link EObject} direct instances of the given EClass (not of subclasses) + * + * @see #getAllInstances(EClass) + */ + public Set getDirectInstances(EClass clazz); + + /** + * Get the all {@link EObject} instances of the given {@link EClass}. + * This includes instances of subclasses. + * + *

+ * Precondition: Results will be returned only if either (a) the EClass (or any superclass) has + * already been registered using {@link #registerEClasses(Set)}, or (b) running in wildcard mode (see + * {@link #isInWildcardMode()}). + * + * @param clazz + * an EClass + * @return the collection of {@link EObject} instances of the given EClass and any of its subclasses + * + * @see #getDirectInstances(EClass) + */ + public Set getAllInstances(EClass clazz); + + /** + * Checks whether the given {@link EObject} is an instance of the given {@link EClass}. + * This includes instances of subclasses. + *

Special note: this method does not check whether the object is indexed in the scope, + * and will return true for out-of-scope objects as well (as long as they are instances of the class). + *

The given class does not have to be indexed. + *

The difference between this method and {@link EClassifier#isInstance(Object)} is that in dynamic EMF mode, EPackage equivalence is taken into account. + * @since 1.6 + */ + public boolean isInstanceOfUnscoped(EObject object, EClass clazz); + + /** + * Checks whether the given {@link EObject} is an instance of the given {@link EClass}. + * This includes instances of subclasses. + *

Special note: this method does check whether the object is indexed in the scope, + * and will return false for out-of-scope objects as well (as long as they are instances of the class). + *

The given class does have to be indexed. + * @since 1.7 + */ + public boolean isInstanceOfScoped(EObject object, EClass clazz); + + /** + * Get the total number of instances of the given {@link EClass} and all of its subclasses. + * + * @since 1.4 + */ + public int countAllInstances(EClass clazz); + + /** + * Find all source {@link EObject}s for which the given feature points to / takes the given value. + * + *

+ * Precondition: Unset / null-valued features are not indexed, so value!=null + * + *

+ * Precondition: Results will be returned only if either (a) the feature has already been + * registered using {@link #registerEStructuralFeatures(Set)}, or (b) running in wildcard mode (see + * {@link #isInWildcardMode()}). + * + * @param value + * the value of the feature + * @param feature + * the feature instance + * @return the collection of {@link EObject} instances + */ + public Set findByFeatureValue(Object value, EStructuralFeature feature); + + /** + * Returns those host {@link EObject}s that have a non-null value for the given feature + * (at least one, in case of multi-valued references). + * + *

+ * Unset / null-valued features are not indexed, and will not be included in the results. + * + *

+ * Precondition: Results will be returned only if either (a) the feature has already been + * registered using {@link #registerEStructuralFeatures(Set)}, or (b) running in wildcard mode (see + * {@link #isInWildcardMode()}). + * + * @param feature + * a structural feature + * @return the collection of {@link EObject}s that have some value for the given structural feature + */ + public Set getHoldersOfFeature(EStructuralFeature feature); + /** + * Returns all non-null values that the given feature takes at least once for any {@link EObject} in the scope + * + *

+ * Unset / null-valued features are not indexed, and will not be included in the results. + * + *

+ * Precondition: Results will be returned only if either (a) the feature has already been + * registered using {@link #registerEStructuralFeatures(Set)}, or (b) running in wildcard mode (see + * {@link #isInWildcardMode()}). + * + * @param feature + * a structural feature + * @return the collection of values that the given structural feature takes + * @since 2.1 + */ + public Set getValuesOfFeature(EStructuralFeature feature); + + /** + * Call this method to dispose the NavigationHelper. + * + *

After its disposal, the NavigationHelper will no longer listen to EMF change notifications, + * and it will be possible to GC it even if the model is retained in memory. + * + *

Precondition:
no listeners can be registered at all. + * @throws IllegalStateException if there are any active listeners + * + */ + public void dispose(); + + /** + * The given listener will be notified from now on whenever instances the given {@link EClass}es + * (and any of their subtypes) are added to or removed from the model. + * + *
+ * Important: Do not call this method from {@link InstanceListener} methods as it may cause a + * {@link ConcurrentModificationException}, if you want to add a listener + * at that point, wrap the call with {@link #executeAfterTraversal(Runnable)}. + * + * @param classes + * the collection of classes whose instances the listener should be notified of + * @param listener + * the listener instance + */ + public void addInstanceListener(Collection classes, InstanceListener listener); + + /** + * Unregisters an instance listener for the given classes. + * + *
+ * Important: Do not call this method from {@link InstanceListener} methods as it may cause a + * {@link ConcurrentModificationException}, if you want to remove a listener at that point, wrap the call with + * {@link #executeAfterTraversal(Runnable)}. + * + * @param classes + * the collection of classes + * @param listener + * the listener instance + */ + public void removeInstanceListener(Collection classes, InstanceListener listener); + + /** + * The given listener will be notified from now on whenever instances the given {@link EDataType}s are + * added to or removed from the model. + * + *
+ * Important: Do not call this method from {@link DataTypeListener} methods as it may cause a + * {@link ConcurrentModificationException}, if you want to add a listener at that point, wrap the call with + * {@link #executeAfterTraversal(Runnable)}. + * + * @param types + * the collection of types associated to the listener + * @param listener + * the listener instance + */ + public void addDataTypeListener(Collection types, DataTypeListener listener); + + /** + * Unregisters a data type listener for the given types. + * + *
+ * Important: Do not call this method from {@link DataTypeListener} methods as it may cause a + * {@link ConcurrentModificationException}, if you want to remove a listener at that point, wrap the call with + * {@link #executeAfterTraversal(Runnable)}. + * + * @param types + * the collection of data types + * @param listener + * the listener instance + */ + public void removeDataTypeListener(Collection types, DataTypeListener listener); + + /** + * The given listener will be notified from now on whenever instances the given + * {@link EStructuralFeature}s are added to or removed from the model. + * + *
+ * Important: Do not call this method from {@link FeatureListener} methods as it may cause a + * {@link ConcurrentModificationException}, if you want to add a listener at that point, wrap the call with + * {@link #executeAfterTraversal(Runnable)}. + * + * @param features + * the collection of features associated to the listener + * @param listener + * the listener instance + */ + public void addFeatureListener(Collection features, FeatureListener listener); + + /** + * Unregisters a feature listener for the given features. + * + *
+ * Important: Do not call this method from {@link FeatureListener} methods as it may cause a + * {@link ConcurrentModificationException}, if you want to remove a listener at that point, wrap the call with + * {@link #executeAfterTraversal(Runnable)}. + * + * @param listener + * the listener instance + * @param features + * the collection of features + */ + public void removeFeatureListener(Collection features, FeatureListener listener); + + /** + * Register a lightweight observer that is notified if the value of any feature of the given EObject changes. + * + *
+ * Important: Do not call this method from {@link LightweightEObjectObserver} methods as it may cause a + * {@link ConcurrentModificationException}, if you want to add an observer at that point, wrap the call with + * {@link #executeAfterTraversal(Runnable)}. + * + * @param observer + * the listener instance + * @param observedObject + * the observed EObject + * @return false if the observer was already attached to the object (call has no effect), true otherwise + */ + public boolean addLightweightEObjectObserver(LightweightEObjectObserver observer, EObject observedObject); + + /** + * Unregisters a lightweight observer for the given EObject. + * + *
+ * Important: Do not call this method from {@link LightweightEObjectObserver} methods as it may cause a + * {@link ConcurrentModificationException}, if you want to remove an observer at that point, wrap the call with + * {@link #executeAfterTraversal(Runnable)}. + * + * @param observer + * the listener instance + * @param observedObject + * the observed EObject + * @return false if the observer has not been previously attached to the object (call has no effect), true otherwise + */ + public boolean removeLightweightEObjectObserver(LightweightEObjectObserver observer, EObject observedObject); + + /** + * Manually turns on indexing for the given types (indexing of others are unaffected). Note that + * registering new types will result in a single iteration through the whole attached model. + * Not usable in wildcard mode. + * + * @param classes + * the set of classes to observe (null okay) + * @param dataTypes + * the set of data types to observe (null okay) + * @param features + * the set of features to observe (null okay) + * @throws IllegalStateException if in wildcard mode + * @since 1.4 + */ + public void registerObservedTypes(Set classes, Set dataTypes, Set features, IndexingLevel level); + + /** + * Manually turns off indexing for the given types (indexing of others are unaffected). Note that if the + * unregistered types are re-registered later, the whole attached model needs to be visited again. + * Not usable in wildcard mode. + * + *
Precondition:
no listeners can be registered for the given types. + * @param classes + * the set of classes that will be ignored again from now on (null okay) + * @param dataTypes + * the set of data types that will be ignored again from now on (null okay) + * @param features + * the set of features that will be ignored again from now on (null okay) + * @throws IllegalStateException if in wildcard mode, or if there are listeners registered for the given types + */ + public void unregisterObservedTypes(Set classes, Set dataTypes, Set features); + + /** + * Manually turns on indexing for the given features (indexing of other features are unaffected). Note that + * registering new features will result in a single iteration through the whole attached model. + * Not usable in wildcard mode. + * + * @param features + * the set of features to observe + * @throws IllegalStateException if in wildcard mode + * @since 1.4 + */ + public void registerEStructuralFeatures(Set features, IndexingLevel level); + + /** + * Manually turns off indexing for the given features (indexing of other features are unaffected). Note that if the + * unregistered features are re-registered later, the whole attached model needs to be visited again. + * Not usable in wildcard mode. + * + *
Precondition:
no listeners can be registered for the given features. + * + * @param features + * the set of features that will be ignored again from now on + * @throws IllegalStateException if in wildcard mode, or if there are listeners registered for the given types + */ + public void unregisterEStructuralFeatures(Set features); + + /** + * Manually turns on indexing for the given classes (indexing of other classes are unaffected). Instances of + * subclasses will also be indexed. Note that registering new classes will result in a single iteration through the whole + * attached model. + * Not usable in wildcard mode. + * + * @param classes + * the set of classes to observe + * @throws IllegalStateException if in wildcard mode + * @since 1.4 + */ + public void registerEClasses(Set classes, IndexingLevel level); + + /** + * Manually turns off indexing for the given classes (indexing of other classes are unaffected). Note that if the + * unregistered classes are re-registered later, the whole attached model needs to be visited again. + * Not usable in wildcard mode. + * + *
Precondition:
no listeners can be registered for the given classes. + * @param classes + * the set of classes that will be ignored again from now on + * @throws IllegalStateException if in wildcard mode, or if there are listeners registered for the given types + */ + public void unregisterEClasses(Set classes); + + /** + * Manually turns on indexing for the given data types (indexing of other features are unaffected). Note that + * registering new data types will result in a single iteration through the whole attached model. + * Not usable in wildcard mode. + * + * @param dataTypes + * the set of data types to observe + * @throws IllegalStateException if in wildcard mode + * @since 1.4 + */ + public void registerEDataTypes(Set dataTypes, IndexingLevel level); + + /** + * Manually turns off indexing for the given data types (indexing of other data types are unaffected). Note that if + * the unregistered data types are re-registered later, the whole attached model needs to be visited again. + * Not usable in wildcard mode. + * + *
Precondition:
no listeners can be registered for the given datatypes. + * + * @param dataTypes + * the set of data types that will be ignored again from now on + * @throws IllegalStateException if in wildcard mode, or if there are listeners registered for the given types + */ + public void unregisterEDataTypes(Set dataTypes); + + /** + * The given callback will be executed, and all model traversals and index registrations will be delayed until the + * execution is done. If there are any outstanding feature, class or datatype registrations, a single coalesced model + * traversal will initialize the caches and deliver the notifications. + * + * @param callable + */ + public V coalesceTraversals(Callable callable) throws InvocationTargetException; + + /** + * Execute the given runnable after traversal. It is guaranteed that the runnable is executed as soon as + * the indexing is finished. The callback is executed only once, then is removed from the callback queue. + * @param traversalCallback + * @throws InvocationTargetException + * @since 1.4 + */ + public void executeAfterTraversal(Runnable traversalCallback) throws InvocationTargetException; + + /** + * Examines whether execution is currently in the callable + * block of an invocation of {#link {@link #coalesceTraversals(Callable)}}. + */ + public boolean isCoalescing(); + + /** + * Adds a coarse-grained listener that will be invoked after the NavigationHelper index or the underlying model is changed. Can be used + * e.g. to check model contents. Not intended for general use. + * + *

See {@link #removeBaseIndexChangeListener(EMFBaseIndexChangeListener)} + * @param listener + */ + public void addBaseIndexChangeListener(EMFBaseIndexChangeListener listener); + + /** + * Removes a registered listener. + * + *

See {@link #addBaseIndexChangeListener(EMFBaseIndexChangeListener)} + * + * @param listener + */ + public void removeBaseIndexChangeListener(EMFBaseIndexChangeListener listener); + + /** + * Adds an additional EMF model root. + * + * @param emfRoot + * @throws ViatraQueryRuntimeException + */ + public void addRoot(Notifier emfRoot); + + /** + * Moves an EObject (along with its entire containment subtree) within the containment hierarchy of the EMF model. + * The object will be relocated from the original parent object to a different parent, or a different containment + * list of the same parent. + * + *

When indexing is enabled, such a relocation is costly if performed through normal getters/setters, as the index + * for the entire subtree is pruned at the old location and reconstructed at the new one. + * This method provides a workaround to keep the operation cheap. + * + *

This method is experimental. Re-entrancy not supported. + * + * @param element the eObject to be moved + * @param targetContainmentReferenceList containment list of the new parent object into which the element has to be moved + * + */ + public void cheapMoveTo(T element, EList targetContainmentReferenceList); + + /** + * Moves an EObject (along with its entire containment subtree) within the containment hierarchy of the EMF model. + * The object will be relocated from the original parent object to a different parent, or a different containment + * list of the same parent. + * + *

When indexing is enabled, such a relocation is costly if performed through normal getters/setters, as the index + * for the entire subtree is pruned at the old location and reconstructed at the new one. + * This method provides a workaround to keep the operation cheap. + * + *

This method is experimental. Re-entrancy not supported. + * + * @param element the eObject to be moved + * @param parent the new parent object under which the element has to be moved + * @param containmentFeature the kind of containment reference that should be established between the new parent and the element + * + */ + public void cheapMoveTo(EObject element, EObject parent, EReference containmentFeature); + + + /** + * Traverses all instances of a selected data type stored in the base index, and allows executing a custom function on + * it. There is no guaranteed order in which the processor will be called with the selected features. + * + * @param type + * @param processor + * @since 0.8 + */ + void processDataTypeInstances(EDataType type, IEDataTypeProcessor processor); + + /** + * Traverses all direct instances of a selected class stored in the base index, and allows executing a custom function on + * it. There is no guaranteed order in which the processor will be called with the selected features. + * + * @param type + * @param processor + * @since 0.8 + */ + void processAllInstances(EClass type, IEClassProcessor processor); + + /** + * Traverses all direct instances of a selected class stored in the base index, and allows executing a custom function on + * it. There is no guaranteed order in which the processor will be called with the selected features. + * + * @param type + * @param processor + * @since 0.8 + */ + void processDirectInstances(EClass type, IEClassProcessor processor); + + /** + * Traverses all instances of a selected feature stored in the base index, and allows executing a custom function on + * it. There is no guaranteed order in which the processor will be called with the selected features. + * + *

+ * Precondition: Will only find those {@link EStructuralFeature}s that have already been registered using + * {@link #registerEStructuralFeatures(Set)}, unless running in wildcard mode (see + * {@link #isInWildcardMode()}). + * + * @since 1.7 + */ + void processAllFeatureInstances(EStructuralFeature feature, IStructuralFeatureInstanceProcessor processor); + /** + * Returns all EClasses that currently have direct instances cached by the index.

    + *
  • Supertypes will not be returned, unless they have direct instances in the model as well. + *
  • If not in wildcard mode, only registered EClasses and their subtypes will be considered. + *
  • Note for advanced users: if a type is represented by multiple EClass objects, one of them is chosen as representative and returned. + *
+ */ + public Set getAllCurrentClasses(); + + /** + * Updates the value of indexed derived features that are not well-behaving. + */ + void resampleDerivedFeatures(); + + /** + * Adds a listener for internal errors in the index. A listener can only be added once. + * + * @param listener + * @returns true if the listener was not already added + * @since 0.8 + */ + boolean addIndexingErrorListener(IEMFIndexingErrorListener listener); + + /** + * Removes a listener for internal errors in the index. + * + * @param listener + * @returns true if the listener was successfully removed (e.g. it did exist) + * @since 0.8 + */ + boolean removeIndexingErrorListener(IEMFIndexingErrorListener listener); + + /** + * Returns the internal, canonicalized implementation of an attribute value. + * + *

Behaviour: when in dynamic EMF mode, substitutes enum literals with a canonical version of the enum literal. + * Otherwise, returns the input. + * + *

The canonical enum literal will be guaranteed to be a valid EMF enum literal ({@link Enumerator}), + * and the best effort is made to ensure that it will be the same for all versions of the {@link EEnum}, + * including {@link EEnumLiteral}s in different versions of ecore packages, as well as Java enums generated from them.. + * + *

Usage is not required when simply querying the indexed model through the {@link NavigationHelper} API, + * as both method inputs and the results returned are automatically canonicalized in dynamic EMF mode. + * Using this method is required only if the client wants to do querying/filtering on the results returned, and wants to know what to look for. + */ + Object toCanonicalValueRepresentation(Object value); + + /** + * @since 1.4 + */ + IndexingLevel getIndexingLevel(EClass type); + + /** + * @since 1.4 + */ + IndexingLevel getIndexingLevel(EDataType type); + + /** + * @since 1.4 + */ + IndexingLevel getIndexingLevel(EStructuralFeature feature); + + /** + * @since 1.4 + */ + public int countDataTypeInstances(EDataType dataType); + + /** + * @since 1.4 + */ + public int countFeatureTargets(EObject seedSource, EStructuralFeature feature); + + /** + * @since 1.4 + */ + public int countFeatures(EStructuralFeature feature); + + +} diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/QueryResultAssociativeStore.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/QueryResultAssociativeStore.java new file mode 100644 index 00000000..27d08506 --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/QueryResultAssociativeStore.java @@ -0,0 +1,322 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Abel Hegedus, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.base.api; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map.Entry; + +import org.apache.log4j.Logger; +import tools.refinery.viatra.runtime.matchers.util.Direction; + +/** + * @author Abel Hegedus + * + */ +public abstract class QueryResultAssociativeStore { + /** + * Error literal returned when associative store modification is attempted without a setter available + */ + protected static final String NOT_ALLOW_MODIFICATIONS = "Query result associative store does not allow modifications"; + + /** + * Logger that can be used for reporting errors during runtime + */ + private Logger logger; + /** + * The collection of listeners registered for this result associative store + */ + private Collection> listeners; + + /** + * The setter registered for changing the contents of the associative store + */ + private IQueryResultSetter setter; + + /** + * @return the listeners + */ + protected Collection> getListeners() { + return listeners; + } + + /** + * @param listeners the listeners to set + */ + protected void setListeners(Collection> listeners) { + this.listeners = listeners; + } + + /** + * @return the setter + */ + protected IQueryResultSetter getSetter() { + return setter; + } + + /** + * @param setter the setter to set + */ + protected void setSetter(IQueryResultSetter setter) { + this.setter = setter; + } + + /** + * @param logger the logger to set + */ + protected void setLogger(Logger logger) { + this.logger = logger; + } + + /** + * Returns the entries in the cache as a collection. + * @return the entries + */ + protected abstract Collection> getCacheEntries(); + + /** + * Registers a listener for this query result associative store that is invoked every time when a key-value pair is inserted + * or removed from the associative store. + * + *

+ * The listener can be unregistered via {@link #removeCallbackOnQueryResultUpdate(IQueryResultUpdateListener)}. + * + * @param listener + * the listener that will be notified of each key-value pair that is inserted or removed, starting from + * now. + * @param fireNow + * if true, notifyPut will be immediately invoked on all current key-values as a one-time effect. + */ + public void addCallbackOnQueryResultUpdate(IQueryResultUpdateListener listener, boolean fireNow) { + if (listeners == null) { + listeners = new HashSet>(); + } + listeners.add(listener); + if(fireNow) { + for (Entry entry : getCacheEntries()) { + sendNotificationToListener(Direction.INSERT, entry.getKey(), entry.getValue(), listener); + } + } + } + + /** + * Unregisters a callback registered by {@link #addCallbackOnQueryResultUpdate(IQueryResultUpdateListener, boolean)} + * . + * + * @param listener + * the listener that will no longer be notified. + */ + public void removeCallbackOnQueryResultUpdate(IQueryResultUpdateListener listener) { + if (listeners != null) { + listeners.remove(listener); + } + } + + /** + * This method notifies the listeners that the query result associative store has changed. + * + * @param direction + * the type of the change (insert or delete) + * @param key + * the key of the pair that changed + * @param value + * the value of the pair that changed + */ + protected void notifyListeners(Direction direction, KeyType key, ValueType value) { + if(listeners != null) { + for (IQueryResultUpdateListener listener : listeners) { + sendNotificationToListener(direction, key, value, listener); + } + } + } + + private void sendNotificationToListener(Direction direction, KeyType key, ValueType value, + IQueryResultUpdateListener listener) { + try { + if (direction == Direction.INSERT) { + listener.notifyPut(key, value); + } else { + listener.notifyRemove(key, value); + } + } catch (Exception e) { // NOPMD + logger.warn( + String.format( + "The query result associative store encountered an error during executing a callback on %s of key %s and value %s. Error message: %s. (Developer note: %s in %s called from QueryResultMultimap)", + direction == Direction.INSERT ? "insertion" : "removal", key, value, e.getMessage(), e + .getClass().getSimpleName(), listener), e); + throw new IllegalStateException("The query result associative store encountered an error during invoking setter",e); + } + } + + /** + * Implementations of QueryResultAssociativeStore can put a new key-value pair into the associative store with this method. If the + * insertion of the key-value pair results in a change, the listeners are notified. + * + *

+ * No validation or null-checking is performed during the method! + * + * @param key + * the key which identifies where the new value is put + * @param value + * the value that is put into the collection of the key + * @return true, if the insertion resulted in a change (the key-value pair was not yet in the associative store) + */ + protected boolean internalPut(KeyType key, ValueType value){ + boolean putResult = internalCachePut(key, value); + if (putResult) { + notifyListeners(Direction.INSERT, key, value); + } + return putResult; + } + /** + * Implementations of QueryResultAssociativeStore can remove a key-value pair from the associative store with this method. If the + * removal of the key-value pair results in a change, the listeners are notified. + * + *

+ * No validation or null-checking is performed during the method! + * + * @param key + * the key which identifies where the value is removed from + * @param value + * the value that is removed from the collection of the key + * @return true, if the removal resulted in a change (the key-value pair was in the associative store) + */ + protected boolean internalRemove(KeyType key, ValueType value){ + boolean removeResult = internalCacheRemove(key, value); + if (removeResult) { + notifyListeners(Direction.DELETE, key, value); + } + return removeResult; + } + + + protected abstract boolean internalCachePut(KeyType key, ValueType value); + protected abstract boolean internalCacheRemove(KeyType key, ValueType value); + protected abstract int internalCacheSize(); + protected abstract boolean internalCacheContainsEntry(KeyType key, ValueType value); + + /** + * @param setter + * the setter to set + */ + public void setQueryResultSetter(IQueryResultSetter setter) { + this.setter = setter; + } + + /** + * @return the logger + */ + protected Logger getLogger() { + return logger; + } + + protected void internalClear() { + if (setter == null) { + throw new UnsupportedOperationException(NOT_ALLOW_MODIFICATIONS); + } + Collection> entries = new ArrayList<>(getCacheEntries()); + Iterator> iterator = entries.iterator(); + while (iterator.hasNext()) { + Entry entry = iterator.next(); + modifyThroughQueryResultSetter(entry.getKey(), entry.getValue(), Direction.DELETE); + } + if (internalCacheSize() != 0) { + StringBuilder sb = new StringBuilder(); + for (Entry entry : getCacheEntries()) { + if (sb.length() > 0) { + sb.append(", "); + } + sb.append(entry.toString()); + } + logger.warn(String + .format("The query result associative store is not empty after clear, remaining entries: %s. (Developer note: %s called from QueryResultMultimap)", + sb.toString(), setter)); + } + } + + /** + * This method is used for calling the query result setter to put or remove a value by modifying the model. + * + *

+ * The given key-value pair is first validated (see {@link IQueryResultSetter#validate(Object, Object)}, then the + * put or remove method is called (see {@link IQueryResultSetter#put(Object, Object)} and + * {@link IQueryResultSetter#remove(Object, Object)}). If the setter reported that the model has been changed, the + * change is checked. + * + *

+ * If the model modification did not change the result set in the desired way, a warning is logged. + * + *

+ * If the setter throws any {@link Throwable}, it is either rethrown in case of {@link Error} and logged otherwise. + * + * + * @param key + * the key of the pair to be inserted or removed + * @param value + * the value of the pair to be inserted or removed + * @param direction + * specifies whether a put or a remove is performed + * @return true, if the associative store changed according to the direction + */ + protected boolean modifyThroughQueryResultSetter(KeyType key, ValueType value, Direction direction) { + try { + if (setter.validate(key, value)) { + final int size = internalCacheSize(); + final int expectedChange = (direction == Direction.INSERT) ? 1 : -1; + boolean changed = false; + if (direction == Direction.INSERT) { + changed = setter.put(key, value); + } else { + changed = setter.remove(key, value); + } + if (changed) { + return checkModificationThroughQueryResultSetter(key, value, direction, expectedChange, size); + } else { + logger.warn(String + .format("The query result associative store %s of key %s and value %s resulted in %s. (Developer note: %s called from QueryResultMultimap)", + direction == Direction.INSERT ? "insertion" : "removal", key, value, + Math.abs(internalCacheSize() - size) > 1 ? "more than one changed result" + : "no changed results", setter)); + } + } + } catch (Exception e) { // NOPMD + logger.warn( + String.format( + "The query result associative store encountered an error during invoking setter on %s of key %s and value %s. Error message: %s. (Developer note: %s in %s called from QueryResultMultimap)", + direction == Direction.INSERT ? "insertion" : "removal", key, value, e.getMessage(), e + .getClass().getSimpleName(), setter), e); + throw new IllegalStateException("The query result associative store encountered an error during invoking setter",e); + } + + return false; + } + + /** + * Checks whether the model modification performed by the {@link IQueryResultSetter} resulted in the insertion or + * removal of exactly the required key-value pair. + * + * @param key + * the key for the pair that was inserted or removed + * @param value + * the value for the pair that was inserted or removed + * @param direction + * the direction of the change + * @param size + * the size of the cache before the change + * @return true, if the changes made by the query result setter were correct + */ + protected boolean checkModificationThroughQueryResultSetter(KeyType key, ValueType value, Direction direction, + final int expectedChange, final int size) { + boolean isInsertion = direction == Direction.INSERT; + return (isInsertion == internalCacheContainsEntry(key, value) + && (internalCacheSize() - expectedChange) == size); + } +} diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/QueryResultMap.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/QueryResultMap.java new file mode 100644 index 00000000..a106ea71 --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/QueryResultMap.java @@ -0,0 +1,210 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Abel Hegedus, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.base.api; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.apache.log4j.Logger; +import tools.refinery.viatra.runtime.matchers.util.Direction; + +/** + * @author Abel Hegedus + * + */ +public abstract class QueryResultMap extends QueryResultAssociativeStore implements Map { + + /** + * This map contains the current key-values. Implementing classes should not modify it directly + */ + private Map cache; + + /** + * Constructor only visible to subclasses. + * + * @param logger + * a logger that can be used for error reporting + */ + protected QueryResultMap(Logger logger) { + cache = new HashMap(); + setLogger(logger); + } + + @Override + protected Collection> getCacheEntries() { + return cache.entrySet(); + } + + @Override + protected boolean internalCachePut(KeyType key, ValueType value) { + ValueType put = cache.put(key, value); + if(put == null) { + return value != null; + } else { + return !put.equals(value); + } + } + + @Override + protected boolean internalCacheRemove(KeyType key, ValueType value) { + ValueType remove = cache.remove(key); + return remove != null; + } + + @Override + protected int internalCacheSize() { + return cache.size(); + } + + @Override + protected boolean internalCacheContainsEntry(KeyType key, ValueType value) { + return cache.containsKey(key) && cache.get(key).equals(value); + } + + /** + * @return the cache + */ + protected Map getCache() { + return cache; + } + + /** + * @param cache + * the cache to set + */ + protected void setCache(Map cache) { + this.cache = cache; + } + + // ======================= implemented Map methods ====================== + + @Override + public void clear() { + internalClear(); + } + + @Override + public boolean containsKey(Object key) { + return cache.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return cache.containsValue(value); + } + + /** + * {@inheritDoc} + * + *

+ * The returned set is immutable. + * + */ + @Override + public Set> entrySet() { + return Collections.unmodifiableSet((Set>) getCacheEntries()); + } + + @Override + public ValueType get(Object key) { + return cache.get(key); + } + + @Override + public boolean isEmpty() { + return cache.isEmpty(); + } + + /** + * {@inheritDoc} + * + *

+ * The returned set is immutable. + * + */ + @Override + public Set keySet() { + return Collections.unmodifiableSet(cache.keySet()); + } + + /** + * {@inheritDoc} + * + *

+ * Throws {@link UnsupportedOperationException} if there is no {@link IQueryResultSetter} + */ + @Override + public ValueType put(KeyType key, ValueType value) { + if (getSetter() == null) { + throw new UnsupportedOperationException(NOT_ALLOW_MODIFICATIONS); + } + ValueType oldValue = cache.get(key); + boolean modified = modifyThroughQueryResultSetter(key, value, Direction.INSERT); + return modified ? oldValue : null; + } + + /** + * {@inheritDoc} + * + *

+ * Throws {@link UnsupportedOperationException} if there is no {@link IQueryResultSetter} + */ + @Override + public void putAll(Map map) { + if (getSetter() == null) { + throw new UnsupportedOperationException(NOT_ALLOW_MODIFICATIONS); + } + for (Entry entry : map.entrySet()) { + modifyThroughQueryResultSetter(entry.getKey(), entry.getValue(), Direction.INSERT); + } + return; + } + + /** + * {@inheritDoc} + * + *

+ * Throws {@link UnsupportedOperationException} if there is no {@link IQueryResultSetter} + */ + @SuppressWarnings("unchecked") + @Override + public ValueType remove(Object key) { + if (getSetter() == null) { + throw new UnsupportedOperationException(NOT_ALLOW_MODIFICATIONS); + } + // if it contains the entry, the types MUST be correct + if (cache.containsKey(key)) { + ValueType value = cache.get(key); + modifyThroughQueryResultSetter((KeyType) key, value, Direction.DELETE); + return value; + } + return null; + } + + @Override + public int size() { + return internalCacheSize(); + } + + /** + * {@inheritDoc} + * + *

+ * The returned collection is immutable. + * + */ + @Override + public Collection values() { + return Collections.unmodifiableCollection(cache.values()); + } + +} diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/TransitiveClosureHelper.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/TransitiveClosureHelper.java new file mode 100644 index 00000000..fc92fef3 --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/TransitiveClosureHelper.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Gabor Bergmann, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.base.api; + +import org.eclipse.emf.ecore.EObject; +import tools.refinery.viatra.runtime.base.itc.igraph.ITcDataSource; + +/** + * The class can be used to compute the transitive closure of a given emf model, where the nodes will be the objects in + * the model and the edges will be represented by the references between them. One must provide the set of references + * that the helper should treat as edges when creating an instance with the factory: only the notifications about these + * references will be handled. + * + * @author Tamas Szabo + * + */ +public interface TransitiveClosureHelper extends ITcDataSource { + +} diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/ViatraBaseFactory.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/ViatraBaseFactory.java new file mode 100644 index 00000000..81bd4f35 --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/ViatraBaseFactory.java @@ -0,0 +1,180 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Gabor Bergmann, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.base.api; + +import java.util.Set; + +import org.apache.log4j.Logger; +import org.eclipse.emf.common.notify.Notifier; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EDataType; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.EStructuralFeature; +import tools.refinery.viatra.runtime.base.core.NavigationHelperImpl; +import tools.refinery.viatra.runtime.base.core.TransitiveClosureHelperImpl; + +/** + * Factory class for the utils in the library:

    + *
  • NavigationHelper (automatic and manual) + *
  • TransitiveClosureUtil + *
+ * + * @author Tamas Szabo + * + */ +public class ViatraBaseFactory { + + private static ViatraBaseFactory instance; + + /** + * Get the singleton instance of ViatraBaseFactory. + * + * @return the singleton instance + */ + public static synchronized ViatraBaseFactory getInstance() { + if (instance == null) { + instance = new ViatraBaseFactory(); + } + + return instance; + } + + protected ViatraBaseFactory() { + super(); + } + + /** + * The method creates a {@link NavigationHelper} index for the given EMF model root. + * A new instance will be created on every call. + *

+ * A NavigationHelper in wildcard mode will process and index all EStructuralFeatures, EClasses and EDatatypes. If + * wildcard mode is off, the client will have to manually register the interesting aspects of the model. + *

+ * The NavigationHelper will be created without dynamic EMF support by default. + * See {@link #createNavigationHelper(Notifier, boolean, boolean, Logger)} for more options. + * + * @see NavigationHelper + * + * @param emfRoot + * the root of the EMF tree to be indexed. Recommended: Resource or ResourceSet. Can be null - you can + * add a root later using {@link NavigationHelper#addRoot(Notifier)} + * @param wildcardMode + * true if all aspects of the EMF model should be indexed automatically, false if manual registration of + * interesting aspects is desirable + * @param logger + * the log output where errors will be logged if encountered during the operation of the + * NavigationHelper; if null, the default logger for {@link NavigationHelper} is used. + * @return the NavigationHelper instance + * @throws ViatraQueryRuntimeException + */ + public NavigationHelper createNavigationHelper(Notifier emfRoot, boolean wildcardMode, Logger logger) { + BaseIndexOptions options = new BaseIndexOptions(false, wildcardMode ? IndexingLevel.FULL : IndexingLevel.NONE); + return createNavigationHelper(emfRoot, options, logger); + } + + /** + * The method creates a {@link NavigationHelper} index for the given EMF model root. + * A new instance will be created on every call. + *

+ * A NavigationHelper in wildcard mode will process and index all EStructuralFeatures, EClasses and EDatatypes. If + * wildcard mode is off, the client will have to manually register the interesting aspects of the model. + *

+ * If the dynamic model flag is set to true, the index will use String ids to distinguish between the various + * {@link EStructuralFeature}, {@link EClass} and {@link EDataType} instances. This way the index is able to + * handle dynamic EMF instance models too. + * + * @see NavigationHelper + * + * @param emfRoot + * the root of the EMF tree to be indexed. Recommended: Resource or ResourceSet. Can be null - you can + * add a root later using {@link NavigationHelper#addRoot(Notifier)} + * @param wildcardMode + * true if all aspects of the EMF model should be indexed automatically, false if manual registration of + * interesting aspects is desirable + * @param dynamicModel + * true if the index should use String ids (nsURIs) for the various EMF types and features, and treat + * multiple EPackages sharing an nsURI as the same. false if dynamic model support is not required + * @param logger + * the log output where errors will be logged if encountered during the operation of the + * NavigationHelper; if null, the default logger for {@link NavigationHelper} is used. + * @return the NavigationHelper instance + * @throws ViatraQueryRuntimeException + */ + public NavigationHelper createNavigationHelper(Notifier emfRoot, boolean wildcardMode, boolean dynamicModel, Logger logger) { + BaseIndexOptions options = new BaseIndexOptions(dynamicModel, wildcardMode ? IndexingLevel.FULL : IndexingLevel.NONE); + return createNavigationHelper(emfRoot, options, logger); + } + + /** + * The method creates a {@link NavigationHelper} index for the given EMF model root. + * A new instance will be created on every call. + *

+ * For details of base index options including wildcard and dynamic EMF mode, see {@link BaseIndexOptions}. + * + * @see NavigationHelper + * + * @param emfRoot + * the root of the EMF tree to be indexed. Recommended: Resource or ResourceSet. Can be null - you can + * add a root later using {@link NavigationHelper#addRoot(Notifier)} + * @param options the options used by the index + * @param logger + * the log output where errors will be logged if encountered during the operation of the + * NavigationHelper; if null, the default logger for {@link NavigationHelper} is used. + * @return the NavigationHelper instance + * @throws ViatraQueryRuntimeException + */ + public NavigationHelper createNavigationHelper(Notifier emfRoot, BaseIndexOptions options, Logger logger) { + Logger l = logger; + if (l == null) + l = Logger.getLogger(NavigationHelper.class); + return new NavigationHelperImpl(emfRoot, options, l); + } + + + + /** + * The method creates a TransitiveClosureHelper instance for the given EMF model root. + * A new instance will be created on every call. + * + *

+ * One must specify the set of EReferences that will be considered as edges. The set can contain multiple elements; + * this way one can query forward and backward reachability information along heterogenous paths. + * + * @param emfRoot + * the root of the EMF tree to be processed. Recommended: Resource or ResourceSet. + * @param referencesToObserve + * the set of references to observe + * @return the TransitiveClosureHelper instance + * @throws ViatraQueryRuntimeException if the creation of the internal NavigationHelper failed + */ + public TransitiveClosureHelper createTransitiveClosureHelper(Notifier emfRoot, Set referencesToObserve) { + return new TransitiveClosureHelperImpl(getInstance().createNavigationHelper(emfRoot, false, null), true, referencesToObserve); + } + + /** + * The method creates a TransitiveClosureHelper instance built on an existing NavigationHelper. + * A new instance will be created on every call. + * + *

+ * One must specify the set of EReferences that will be considered as edges. The set can contain multiple elements; + * this way one can query forward and backward reachability information along heterogenous paths. + * + * @param baseIndex + * the already existing NavigationHelper index on the model + * @param referencesToObserve + * the set of references to observe + * @return the TransitiveClosureHelper instance + */ + public TransitiveClosureHelper createTransitiveClosureHelper(NavigationHelper baseIndex, Set referencesToObserve) { + return new TransitiveClosureHelperImpl(baseIndex, false, referencesToObserve); + } + + +} diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/filters/IBaseIndexFeatureFilter.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/filters/IBaseIndexFeatureFilter.java new file mode 100644 index 00000000..8929b2ab --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/filters/IBaseIndexFeatureFilter.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2010-2016, Peter Lunk, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.viatra.runtime.base.api.filters; + +import org.eclipse.emf.ecore.EStructuralFeature; + +/** + * + * Defines if an {@link EStructuralFeature} should not be indexed by VIATRA Base. This filtering + * method should only be used if the input metamodel has certain features, that the base indexer + * cannot handle. If the filtered feature is a containment feature, the whole sub-tree accessible + * through the said feature will be filtered. + * + * Note: This API feature is for advanced users only. Usage of this feature is not encouraged, + * unless the filtering task is impossible via using the more straightforward + * {@link IBaseIndexResourceFilter} or {@link IBaseIndexObjectFilter}. + * + * @author Peter Lunk + * @since 1.5 + * + */ +public interface IBaseIndexFeatureFilter { + + /** + * Decides whether the selected {@link EStructuralFeature} is filtered. + * + * @param feature + * @return true, if the feature should not be indexed + */ + boolean isFiltered(EStructuralFeature feature); + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/filters/IBaseIndexObjectFilter.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/filters/IBaseIndexObjectFilter.java new file mode 100644 index 00000000..e1e46bbd --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/filters/IBaseIndexObjectFilter.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.base.api.filters; + +import org.eclipse.emf.common.notify.Notifier; + +/** + * + * Stores a collection of {@link Notifier} instances that need not to be indexed by VIATRA Base. + * + * @author Zoltan Ujhelyi + * + */ +public interface IBaseIndexObjectFilter { + + /** + * Decides whether the selected notifier is filtered. + * + * @param notifier + * @return true, if the notifier should not be indexed + */ + boolean isFiltered(Notifier notifier); + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/filters/IBaseIndexResourceFilter.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/filters/IBaseIndexResourceFilter.java new file mode 100644 index 00000000..73d3e961 --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/filters/IBaseIndexResourceFilter.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.base.api.filters; + +import org.eclipse.emf.ecore.resource.Resource; + +/** + * Defines a filter for indexing resources + * @author Zoltan Ujhelyi + * + */ +public interface IBaseIndexResourceFilter { + + /** + * Decides whether a selected resource needs to be indexed + * @param resource + * @return true, if the selected resource is filtered + */ + boolean isResourceFiltered(Resource resource); + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/filters/SimpleBaseIndexFilter.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/filters/SimpleBaseIndexFilter.java new file mode 100644 index 00000000..9ae88a1a --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/filters/SimpleBaseIndexFilter.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.base.api.filters; + +import org.eclipse.emf.common.notify.Notifier; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +/** + * An index filter that is based on a collection of {@link Notifier} instances. + * + * @author Zoltan Ujhelyi + * + */ +public class SimpleBaseIndexFilter implements IBaseIndexObjectFilter { + + Set filters; + + /** + * Creates a filter using a collection of (Resource and) Notifier instances. Every containment subtree, selected by + * the given Notifiers are filtered out. + * + * @param filterConfiguration + */ + public SimpleBaseIndexFilter(Collection filterConfiguration) { + filters = new HashSet<>(filterConfiguration); + } + + public SimpleBaseIndexFilter(SimpleBaseIndexFilter other) { + this(other.filters); + } + + @Override + public boolean isFiltered(Notifier notifier) { + return filters.contains(notifier); + } + +} diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/profiler/BaseIndexProfiler.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/profiler/BaseIndexProfiler.java new file mode 100644 index 00000000..d3cc152e --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/profiler/BaseIndexProfiler.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.base.api.profiler; + +import tools.refinery.viatra.runtime.base.api.NavigationHelper; +import tools.refinery.viatra.runtime.base.core.NavigationHelperContentAdapter; +import tools.refinery.viatra.runtime.base.core.NavigationHelperImpl; +import tools.refinery.viatra.runtime.base.core.profiler.ProfilingNavigationHelperContentAdapter; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; + +/** + * An index profiler can be attached to an existing navigation helper instance to access the profiling data and control + * the profiler itself. If the NavigationHelper was not started in profiling mode, the profiler cannot be initialized. + * + * @since 2.3 + */ +public class BaseIndexProfiler { + + ProfilingNavigationHelperContentAdapter adapter; + + /** + * + * @throws IllegalArgumentException if the profiler cannot be attached to the base index instance + */ + public BaseIndexProfiler(NavigationHelper navigationHelper) { + if (navigationHelper instanceof NavigationHelperImpl) { + final NavigationHelperContentAdapter contentAdapter = ((NavigationHelperImpl) navigationHelper).getContentAdapter(); + if (contentAdapter instanceof ProfilingNavigationHelperContentAdapter) { + adapter = (ProfilingNavigationHelperContentAdapter)contentAdapter; + } + } + Preconditions.checkArgument(adapter != null, "Cannot attach profiler to Base Index"); + } + + /** + * Returns the number of external request (e.g. model changes) the profiler recorded. + */ + public long getNotificationCount() { + return adapter.getNotificationCount(); + } + + /** + * Return the total time base index profiler recorded for reacting to model operations. + */ + public long getTotalMeasuredTimeInMS() { + return adapter.getTotalMeasuredTimeInMS(); + } + + /** + * Returns whether the profiler is turned on (e.g. measured values are increased). + */ + public boolean isEnabled() { + return adapter.isEnabled(); + } + + /** + * Enables the base index profiling (e.g. measured values are increased) + */ + public void setEnabled(boolean isEnabled) { + adapter.setEnabled(isEnabled); + } + + /** + * Resets all measurements to 0, regardless whether the profiler is enabled or not. + *

+ * + * Note: The behavior of the profiler is undefined when the measurements are reset while an EMF + * notification is being processed and the profiler is enabled. + */ + public void resetMeasurement() { + adapter.resetMeasurement(); + } +} diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/profiler/ProfilerMode.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/profiler/ProfilerMode.java new file mode 100644 index 00000000..74901263 --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/profiler/ProfilerMode.java @@ -0,0 +1,22 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.base.api.profiler; + +/** + * @since 2.3 + */ +public enum ProfilerMode { + + /** The base index profiler is not available */ + OFF, + /** The profiler is initialized but not started until necessary */ + START_DISABLED, + /** The profiler is initialized and started by default */ + START_ENABLED +} diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/comprehension/EMFModelComprehension.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/comprehension/EMFModelComprehension.java new file mode 100644 index 00000000..bde93367 --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/comprehension/EMFModelComprehension.java @@ -0,0 +1,356 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.base.comprehension; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.eclipse.emf.common.notify.Notifier; +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.EAttribute; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.emf.ecore.EcorePackage; +import org.eclipse.emf.ecore.InternalEObject; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.eclipse.emf.ecore.util.ExtendedMetaData; +import org.eclipse.emf.ecore.util.FeatureMap; +import org.eclipse.emf.ecore.util.FeatureMap.Entry; +import org.eclipse.emf.ecore.util.InternalEList; +import tools.refinery.viatra.runtime.base.api.BaseIndexOptions; +import tools.refinery.viatra.runtime.base.api.filters.IBaseIndexFeatureFilter; +import tools.refinery.viatra.runtime.base.api.filters.IBaseIndexObjectFilter; +import tools.refinery.viatra.runtime.base.api.filters.IBaseIndexResourceFilter; + +/** + * @author Bergmann Gábor + * + * Does not directly visit derived links, unless marked as a WellBehavingFeature. Derived edges are + * automatically interpreted correctly in these cases: - EFeatureMaps - eOpposites of containments + * + * @noextend This class is not intended to be subclassed by clients. + */ +public class EMFModelComprehension { + + /** + * @since 2.3 + */ + protected BaseIndexOptions options; + + /** + * Creates a model comprehension with the specified options. The options are copied, therefore subsequent changes + * will not affect the comprehension. + */ + public EMFModelComprehension(BaseIndexOptions options) { + this.options = options.copy(); + } + + /** + * Should not traverse this feature directly. It is still possible that it can be represented in IQBase if + * {@link #representable(EStructuralFeature)} is true. + */ + public boolean untraversableDirectly(EStructuralFeature feature) { + + if((feature instanceof EReference && ((EReference)feature).isContainer())) { + // container features are always represented through their opposite + return true; + } + + //If the feature is filtered by the feature filter specified in the BaseIndexOptions, return true + final IBaseIndexFeatureFilter featureFilter = options.getFeatureFilterConfiguration(); + if(featureFilter != null && featureFilter.isFiltered(feature)){ + return true; + } + + boolean suspect = onlySamplingFeature(feature); + if(suspect) { + // even if the feature can only be sampled, it may be used if the proper base index option is set + suspect = options.isTraverseOnlyWellBehavingDerivedFeatures(); + } + return suspect; + } + + /** + * Decides whether a feature can only be sampled as there is no guarantee that proper notifications will be + * delivered by their implementation. + * + *

Such features are derived (and/or volatile) features that are not well-behaving. + */ + public boolean onlySamplingFeature(EStructuralFeature feature) { + boolean suspect = + feature.isDerived() || + feature.isVolatile(); + if (suspect) { + // override support here + // (e.g. if manual notifications available, or no changes expected afterwards) + suspect = !WellbehavingDerivedFeatureRegistry.isWellbehavingFeature(feature); + // TODO verbose flag somewhere to ease debugging (for such warnings) + // TODO add warning about not visited subtree (containment, FeatureMap and annotation didn't define + // otherwise) + } + return suspect; + } + + /** + * This feature can be represented in IQBase. + */ + public boolean representable(EStructuralFeature feature) { + if (!untraversableDirectly(feature)) + return true; + + if (feature instanceof EReference) { + final EReference reference = (EReference) feature; + if (reference.isContainer() && representable(reference.getEOpposite())) + return true; + } + + boolean isMixed = "mixed".equals(EcoreUtil.getAnnotation(feature.getEContainingClass(), + ExtendedMetaData.ANNOTATION_URI, "kind")); + if (isMixed) + return true; // TODO maybe check the "name"=":mixed" or ":group" feature for representability? + + // Group features are alternative features that are used when the ecore is derived from an xsd schema containing + // choices; in that case instead of the asked feature we should index the corresponding group feature + final EStructuralFeature groupFeature = ExtendedMetaData.INSTANCE.getGroup(feature); + if (groupFeature != null) { + return representable(groupFeature); + } + + return false; + } + + /** + * Resource filters not consulted here (for performance), because model roots are assumed to be pre-filtered. + */ + public void traverseModel(EMFVisitor visitor, Notifier source) { + if (source == null) + return; + if (source instanceof EObject) { + final EObject sourceObject = (EObject) source; + if (sourceObject.eIsProxy()) + throw new IllegalArgumentException("Proxy EObject cannot act as model roots for VIATRA: " + source); + traverseObject(visitor, sourceObject); + } else if (source instanceof Resource) { + traverseResource(visitor, (Resource) source); + } else if (source instanceof ResourceSet) { + traverseResourceSet(visitor, (ResourceSet) source); + } + } + + public void traverseResourceSet(EMFVisitor visitor, ResourceSet source) { + if (source == null) + return; + final List resources = new ArrayList(source.getResources()); + for (Resource resource : resources) { + traverseResourceIfUnfiltered(visitor, resource); + } + } + + public void traverseResourceIfUnfiltered(EMFVisitor visitor, Resource resource) { + final IBaseIndexResourceFilter resourceFilter = options.getResourceFilterConfiguration(); + if (resourceFilter != null && resourceFilter.isResourceFiltered(resource)) + return; + final IBaseIndexObjectFilter objectFilter = options.getObjectFilterConfiguration(); + if (objectFilter != null && objectFilter.isFiltered(resource)) + return; + + traverseResource(visitor, resource); + } + + public void traverseResource(EMFVisitor visitor, Resource source) { + if (source == null) + return; + if (visitor.pruneSubtrees(source)) + return; + final EList contents = source.getContents(); + for (EObject eObject : contents) { + traverseObjectIfUnfiltered(visitor, eObject); + } + } + + + public void traverseObjectIfUnfiltered(EMFVisitor visitor, EObject targetObject) { + final IBaseIndexObjectFilter objectFilter = options.getObjectFilterConfiguration(); + if (objectFilter != null && objectFilter.isFiltered(targetObject)) + return; + + traverseObject(visitor, targetObject); + } + + public void traverseObject(EMFVisitor visitor, EObject source) { + if (source == null) + return; + + if (visitor.preOrder()) visitor.visitElement(source); + for (EStructuralFeature feature : source.eClass().getEAllStructuralFeatures()) { + if (untraversableDirectly(feature)) + continue; + final boolean visitorPrunes = visitor.pruneFeature(feature); + if (visitorPrunes && !unprunableFeature(visitor, source, feature)) + continue; + + traverseFeatureTargets(visitor, source, feature, visitorPrunes); + } + if (!visitor.preOrder()) visitor.visitElement(source); + } + + protected void traverseFeatureTargets(EMFVisitor visitor, EObject source, EStructuralFeature feature, + final boolean visitorPrunes) { + boolean attemptResolve = (feature instanceof EAttribute) || visitor.attemptProxyResolutions(source, (EReference)feature); + if (feature.isMany()) { + EList targets = (EList) source.eGet(feature); + int position = 0; + Iterator iterator = attemptResolve ? targets.iterator() : ((InternalEList)targets).basicIterator(); + while (iterator.hasNext()) { + Object target = iterator.next(); + traverseFeatureInternal(visitor, source, feature, target, visitorPrunes, position++); + } + } else { + Object target = source.eGet(feature, attemptResolve); + if (target != null) + traverseFeatureInternal(visitor, source, feature, target, visitorPrunes, null); + } + } + /** + * @since 2.3 + */ + protected boolean unprunableFeature(EMFVisitor visitor, EObject source, EStructuralFeature feature) { + return (feature instanceof EAttribute && EcorePackage.eINSTANCE.getEFeatureMapEntry().equals( + ((EAttribute) feature).getEAttributeType())) + || (feature instanceof EReference && ((EReference) feature).isContainment() && (!visitor + .pruneSubtrees(source) || ((EReference) feature).getEOpposite() != null)); + } + + /** + * @param position optional: known position in multivalued collection (for more efficient proxy resolution) + */ + public void traverseFeature(EMFVisitor visitor, EObject source, EStructuralFeature feature, Object target, Integer position) { + if (target == null) + return; + if (untraversableDirectly(feature)) + return; + traverseFeatureInternalSimple(visitor, source, feature, target, position); + } + + /** + * @param position optional: known position in multivalued collection (for more efficient proxy resolution) + * @since 2.3 + */ + protected void traverseFeatureInternalSimple(EMFVisitor visitor, EObject source, EStructuralFeature feature, + Object target, Integer position) { + final boolean visitorPrunes = visitor.pruneFeature(feature); + if (visitorPrunes && !unprunableFeature(visitor, source, feature)) + return; + + traverseFeatureInternal(visitor, source, feature, target, visitorPrunes, position); + } + + /** + * @pre target != null + * @param position optional: known position in multivalued collection (for more efficient proxy resolution) + * @since 2.3 + */ + protected void traverseFeatureInternal(EMFVisitor visitor, EObject source, EStructuralFeature feature, + Object target, boolean visitorPrunes, Integer position) { + if (feature instanceof EAttribute) { + if (!visitorPrunes) + visitor.visitAttribute(source, (EAttribute) feature, target); + if (target instanceof FeatureMap.Entry) { // emulated derived edge based on FeatureMap + Entry entry = (FeatureMap.Entry) target; + final EStructuralFeature emulated = entry.getEStructuralFeature(); + final Object emulatedTarget = entry.getValue(); + + emulateUntraversableFeature(visitor, source, emulated, emulatedTarget); + } + } else if (feature instanceof EReference) { + EReference reference = (EReference) feature; + EObject targetObject = (EObject) target; + if (reference.isContainment()) { + if (!visitor.avoidTransientContainmentLink(source, reference, targetObject)) { + if (!visitorPrunes) + visitor.visitInternalContainment(source, reference, targetObject); + if (!visitor.pruneSubtrees(source)) { + // Recursively follow containment... + // unless cross-resource containment (in which case we may skip) + Resource targetResource = (targetObject instanceof InternalEObject)? + ((InternalEObject)targetObject).eDirectResource() : null; + boolean crossResourceContainment = targetResource != null; + if (!crossResourceContainment || visitor.descendAlongCrossResourceContainments()) { + // in-resource containment shall be followed + // as well as cross-resource containment for an object scope + traverseObjectIfUnfiltered(visitor, targetObject); + } else { + // do not follow + // target will be traversed separately from its resource (resourceSet scope) + // or left out of scope (resource scope) + } + } + + final EReference opposite = reference.getEOpposite(); + if (opposite != null) { // emulated derived edge based on container opposite + emulateUntraversableFeature(visitor, targetObject, opposite, source); + } + } + } else { + // if (containedElements.contains(target)) + if (!visitorPrunes) + visitor.visitNonContainmentReference(source, reference, targetObject); + } + if (targetObject.eIsProxy()) { + if (!reference.isResolveProxies()) { + throw new IllegalStateException(String.format( + "EReference '%s' of EClass %s is set as proxy-non-resolving (i.e. it should never point to a proxy, and never lead cross-resource), " + + "yet VIATRA Base encountered a proxy object %s referenced from %s.", + reference.getName(), reference.getEContainingClass().getInstanceTypeName(), + targetObject, source)); + } + visitor.visitProxyReference(source, reference, targetObject, position); + } + } + + } + + + /** + * Emulates a derived edge, if it is not visited otherwise + * + * @pre target != null + * @since 2.3 + */ + protected void emulateUntraversableFeature(EMFVisitor visitor, EObject source, + final EStructuralFeature emulated, final Object target) { + if (untraversableDirectly(emulated)) + traverseFeatureInternalSimple(visitor, source, emulated, target, null); + } + + /** + * Can be called to attempt to resolve a reference pointing to one or more proxies, using eGet(). + */ + @SuppressWarnings("unchecked") + public void tryResolveReference(EObject source, EReference reference) { + final Object result = source.eGet(reference, true); + if (reference.isMany()) { + // no idea which element to get, have to iterate through + ((Iterable) result).forEach(EObject -> {/*proxy resolution as a side-effect of traversal*/}); + } + } + + /** + * Finds out whether the Resource is currently loading + */ + public boolean isLoading(Resource resource) { + return !resource.isLoaded() || ((Resource.Internal)resource).isLoading(); + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/comprehension/EMFVisitor.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/comprehension/EMFVisitor.java new file mode 100644 index 00000000..6029bb02 --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/comprehension/EMFVisitor.java @@ -0,0 +1,145 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.base.comprehension; + +import org.eclipse.emf.ecore.EAttribute; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.emf.ecore.resource.Resource; + +/** + * Use EMFModelComprehension to visit an EMF model. + * + * @author Bergmann Gábor + * + */ +// FIXME: +// - handle boundary of active emfRoot subtree +// - more efficient traversal +public class EMFVisitor { + + boolean preOrder; + + public EMFVisitor(boolean preOrder) { + super(); + this.preOrder = preOrder; + } + + /** + * @param resource + * @param element + */ + public void visitTopElementInResource(Resource resource, EObject element) { + } + + /** + * @param resource + */ + public void visitResource(Resource resource) { + } + + /** + * @param source + */ + public void visitElement(EObject source) { + } + + /** + * @param source + * @param feature + * @param target + */ + public void visitNonContainmentReference(EObject source, EReference feature, EObject target) { + } + + /** + * @param source + * @param feature + * @param target + */ + public void visitInternalContainment(EObject source, EReference feature, EObject target) { + } + + /** + * @param source + * @param feature + * @param target + */ + public void visitAttribute(EObject source, EAttribute feature, Object target) { + } + + /** + * Returns true if the given feature should not be traversed (interesting esp. if multi-valued) + */ + public boolean pruneFeature(EStructuralFeature feature) { + return false; + } + + /** + * Returns true if the contents of an object should be pruned (and not explored by the visitor) + */ + public boolean pruneSubtrees(EObject source) { + return false; + } + + /** + * Returns true if the contents of a resource should be pruned (and not explored by the visitor) + */ + public boolean pruneSubtrees(Resource source) { + return false; + } + + /** + * An opportunity for the visitor to indicate that the containment link is considered in a transient state, and the + * model comprehension should avoid following it. + * + * A containment is in a transient state from the point of view of the visitor if it connects a subtree that is + * being inserted during a full-model traversal, and a separate notification handler will deal with it + * later. + */ + public boolean avoidTransientContainmentLink(EObject source, EReference reference, EObject targetObject) { + return false; + } + + /** + * @return if objects should be visited before their outgoing edges + */ + public boolean preOrder() { + return preOrder; + } + + /** + * Called after visiting the reference, if the target is a proxy. + * + * @param position + * optional: known position in multivalued collection (for more efficient proxy resolution) + */ + public void visitProxyReference(EObject source, EReference reference, EObject targetObject, Integer position) { + } + + /** + * Whether the given reference of the given object should be resolved when it is a proxy + */ + public boolean attemptProxyResolutions(EObject source, EReference feature) { + return true; + } + + /** + * @return true if traversing visitors shall descend along cross-resource containments + * (this only makes sense for traversing visitors on an object scope) + * + * @since 1.7 + */ + public boolean descendAlongCrossResourceContainments() { + return false; + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/comprehension/WellbehavingDerivedFeatureRegistry.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/comprehension/WellbehavingDerivedFeatureRegistry.java new file mode 100644 index 00000000..d696ddd6 --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/comprehension/WellbehavingDerivedFeatureRegistry.java @@ -0,0 +1,154 @@ +/******************************************************************************* + * Copyright (c) 2004-2011 Abel Hegedus and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.base.comprehension; + +import java.util.Collection; +import java.util.Collections; +import java.util.WeakHashMap; + +import org.apache.log4j.Logger; +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.IExtension; +import org.eclipse.core.runtime.IExtensionPoint; +import org.eclipse.core.runtime.IExtensionRegistry; +import org.eclipse.core.runtime.Platform; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EClassifier; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.EStructuralFeature; +import tools.refinery.viatra.runtime.base.ViatraBasePlugin; + +/** + * @author Abel Hegedus + * + */ +public class WellbehavingDerivedFeatureRegistry { + + + private static Collection contributedWellbehavingDerivedFeatures = Collections.newSetFromMap(new WeakHashMap()); + private static Collection contributedWellbehavingDerivedClasses = Collections.newSetFromMap(new WeakHashMap()); + private static Collection contributedWellbehavingDerivedPackages = Collections.newSetFromMap(new WeakHashMap()); + + private WellbehavingDerivedFeatureRegistry() { + } + + /** + * Called by ViatraBasePlugin. + */ + public static void initRegistry() { + getContributedWellbehavingDerivedFeatures().clear(); + getContributedWellbehavingDerivedClasses().clear(); + getContributedWellbehavingDerivedPackages().clear(); + + IExtensionRegistry reg = Platform.getExtensionRegistry(); + IExtensionPoint poi; + + poi = reg.getExtensionPoint(ViatraBasePlugin.WELLBEHAVING_DERIVED_FEATURE_EXTENSION_POINT_ID); + if (poi != null) { + IExtension[] exts = poi.getExtensions(); + + for (IExtension ext : exts) { + + IConfigurationElement[] els = ext.getConfigurationElements(); + for (IConfigurationElement el : els) { + if (el.getName().equals("wellbehaving-derived-feature")) { + processWellbehavingExtension(el); + } else { + throw new UnsupportedOperationException("Unknown configuration element " + el.getName() + + " in plugin.xml of " + el.getDeclaringExtension().getUniqueIdentifier()); + } + } + } + } + } + + private static void processWellbehavingExtension(IConfigurationElement el) { + try { + String packageUri = el.getAttribute("package-nsUri"); + String featureName = el.getAttribute("feature-name"); + String classifierName = el.getAttribute("classifier-name"); + String contributorName = el.getContributor().getName(); + StringBuilder featureIdBuilder = new StringBuilder(); + if (packageUri != null) { + EPackage pckg = EPackage.Registry.INSTANCE.getEPackage(packageUri); + featureIdBuilder.append(packageUri); + if (pckg != null) { + if (classifierName != null) { + EClassifier clsr = pckg.getEClassifier(classifierName); + featureIdBuilder.append("##").append(classifierName); + if (clsr instanceof EClass) { + if (featureName != null) { + EClass cls = (EClass) clsr; + EStructuralFeature feature = cls.getEStructuralFeature(featureName); + featureIdBuilder.append("##").append(featureName); + if (feature != null) { + registerWellbehavingDerivedFeature(feature); + } else { + throw new IllegalStateException(String.format("Feature %s of EClass %s in package %s not found! (plug-in %s)", featureName, classifierName, packageUri, contributorName)); + } + } else { + registerWellbehavingDerivedClass((EClass) clsr); + } + } else { + throw new IllegalStateException(String.format("EClassifier %s does not exist in package %s! (plug-in %s)", classifierName, packageUri, contributorName)); + } + } else { + if(featureName != null){ + throw new IllegalStateException(String.format("Feature name must be empty if classifier name is not set! (package %s, plug-in %s)", packageUri, contributorName)); + } + registerWellbehavingDerivedPackage(pckg); + } + } + } + } catch (Exception e) { + final Logger logger = Logger.getLogger(WellbehavingDerivedFeatureRegistry.class); + logger.error("Well-behaving feature registration failed", e); + } + } + + /** + * + * @param feature + * @return true if the feature (or its defining EClass or ) is registered as well-behaving + */ + public static boolean isWellbehavingFeature(EStructuralFeature feature) { + if(feature == null){ + return false; + } else if (contributedWellbehavingDerivedFeatures.contains(feature)) { + return true; + } else if (contributedWellbehavingDerivedClasses.contains(feature.getEContainingClass())) { + return true; + } else return contributedWellbehavingDerivedPackages.contains(feature.getEContainingClass().getEPackage()); + } + + public static void registerWellbehavingDerivedFeature(EStructuralFeature feature) { + contributedWellbehavingDerivedFeatures.add(feature); + } + + public static void registerWellbehavingDerivedClass(EClass cls) { + contributedWellbehavingDerivedClasses.add(cls); + } + + public static void registerWellbehavingDerivedPackage(EPackage pkg) { + contributedWellbehavingDerivedPackages.add(pkg); + } + + public static Collection getContributedWellbehavingDerivedFeatures() { + return contributedWellbehavingDerivedFeatures; + } + + public static Collection getContributedWellbehavingDerivedClasses() { + return contributedWellbehavingDerivedClasses; + } + + public static Collection getContributedWellbehavingDerivedPackages() { + return contributedWellbehavingDerivedPackages; + } + +} diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/AbstractBaseIndexStore.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/AbstractBaseIndexStore.java new file mode 100644 index 00000000..3d61b1bf --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/AbstractBaseIndexStore.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.base.core; + +import org.apache.log4j.Logger; +import tools.refinery.viatra.runtime.base.api.BaseIndexOptions; + +/** + * @since 1.6 + */ +public class AbstractBaseIndexStore { + + protected final NavigationHelperImpl navigationHelper; + protected final Logger logger; + protected final BaseIndexOptions options; + + public AbstractBaseIndexStore(NavigationHelperImpl navigationHelper, Logger logger) { + this.navigationHelper = navigationHelper; + this.logger = logger; + this.options = navigationHelper.getBaseIndexOptions(); + } +} diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/EMFBaseIndexInstanceStore.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/EMFBaseIndexInstanceStore.java new file mode 100644 index 00000000..2094bbbe --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/EMFBaseIndexInstanceStore.java @@ -0,0 +1,451 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Gabor Bergmann, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.base.core; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.apache.log4j.Logger; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EClassifier; +import org.eclipse.emf.ecore.EObject; +import tools.refinery.viatra.runtime.base.api.IStructuralFeatureInstanceProcessor; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.IMultiset; + +/** + * Stores the indexed contents of an EMF model + * (includes instance model information). + * + * @author Gabor Bergmann + * @noextend This class is not intended to be subclassed by clients. + */ +public class EMFBaseIndexInstanceStore extends AbstractBaseIndexStore { + + public EMFBaseIndexInstanceStore(NavigationHelperImpl navigationHelper, Logger logger) { + super(navigationHelper, logger); + } + + /** + * since last run of after-update callbacks + */ + boolean isDirty = false; + + /** + * feature (EAttribute or EReference or equivalent String key) -> FeatureData + * @since 1.7 + */ + private Map featureDataMap = CollectionsFactory.createMap(); + + /** + * value -> featureKey(s); + * constructed on-demand, null if unused (hopefully most of the time) + */ + private Map> valueToFeatureMap = null; + + + /** + * key (String id or EClass instance) -> instance(s) + */ + private final Map> instanceMap = CollectionsFactory.createMap(); + + /** + * key (String id or EDataType instance) -> multiset of value(s) + */ + private final Map> dataTypeMap = CollectionsFactory.createMap(); + + /** + * Bundles all instance store data specific to a given binary feature. + * + *

TODO: specialize for to-one features and unique to-many features + *

TODO: on-demand construction of valueToHolderMap + * + * @author Gabor Bergmann + * @since 1.7 + */ + class FeatureData { + /** value -> holder(s) */ + private Map> valueToHolderMap = CollectionsFactory.createMap(); + /** + * holder -> value(s); + * constructed on-demand, null if unused + */ + private Map> holderToValueMap; + + /** + * feature (EAttribute or EReference) or its string key (in dynamic EMF mode) + */ + private Object featureKey; + + /** + * @return feature (EAttribute or EReference) or its string key (in dynamic EMF mode) + */ + public Object getFeatureKey() { + return featureKey; + } + + @Override + public String toString() { + return this.getClass().getSimpleName() + ":" + featureKey; + } + + /** + * @return true if this was the first time the value was added to this feature of this holder (false is only + * expected for non-unique features) + */ + boolean insertFeatureTuple(boolean unique, final Object value, final EObject holder) { + // TODO we currently assume V2H map exists + boolean changed = addToValueToHolderMap(value, holder); + if (holderToValueMap != null) { + addToHolderToValueMap(value, holder); + } + + if (unique && !changed) { + navigationHelper.logIncidentFeatureTupleInsertion(value, holder, featureKey); + } + return changed; + } + + /** + * @return true if this was the last duplicate of the value added to this feature of this holder (false is only + * expected for non-unique features) + */ + boolean removeFeatureTuple(boolean unique, final Object value, final EObject holder) { + Object featureKey = getFeatureKey(); + try { + // TODO we currently assume V2H map exists + boolean changed = removeFromValueToHolderMap(value, holder); + if (holderToValueMap != null) { + removeFromHolderToValueMap(value, holder); + } + + if (unique && !changed) { + navigationHelper.logIncidentFeatureTupleRemoval(value, holder, featureKey); + } + return changed; + } catch (IllegalStateException ex) { + navigationHelper.logIncidentFeatureTupleRemoval(value, holder, featureKey); + return false; + } + } + + + protected boolean addToHolderToValueMap(Object value, EObject holder) { + IMultiset values = holderToValueMap.computeIfAbsent(holder, + CollectionsFactory::emptyMultiset); + boolean changed = values.addOne(value); + return changed; + } + + protected boolean addToValueToHolderMap(final Object value, final EObject holder) { + IMultiset holders = valueToHolderMap.computeIfAbsent(value, + CollectionsFactory::emptyMultiset); + boolean changed = holders.addOne(holder); + return changed; + } + + protected boolean removeFromHolderToValueMap(Object value, EObject holder) throws IllegalStateException { + IMultiset values = holderToValueMap.get(holder); + if (values == null) + throw new IllegalStateException(); + boolean changed = values.removeOne(value); + if (changed && values.isEmpty()) + holderToValueMap.remove(holder); + return changed; + } + protected boolean removeFromValueToHolderMap(final Object value, final EObject holder) throws IllegalStateException { + IMultiset holders = valueToHolderMap.get(value); + if (holders == null) + throw new IllegalStateException(); + boolean changed = holders.removeOne(holder); + if (changed && holders.isEmpty()) + valueToHolderMap.remove(value); + return changed; + } + + protected Map> getHolderToValueMap() { + if (holderToValueMap == null) { + holderToValueMap = CollectionsFactory.createMap(); + + // TODO we currently assume V2H map exists + for (Entry> entry : valueToHolderMap.entrySet()) { + Object value = entry.getKey(); + IMultiset holders = entry.getValue(); + for (EObject holder : holders.distinctValues()) { + int count = holders.getCount(holder); + + IMultiset valuesOfHolder = holderToValueMap.computeIfAbsent(holder, + CollectionsFactory::emptyMultiset); + valuesOfHolder.addPositive(value, count); + } + } + } + return holderToValueMap; + } + protected Map> getValueToHolderMap() { + // TODO we currently assume V2H map exists + return valueToHolderMap; + } + + public void forEach(IStructuralFeatureInstanceProcessor processor) { + // TODO we currently assume V2H map exists + if (valueToHolderMap != null) { + for (Entry> entry : valueToHolderMap.entrySet()) { + Object value = entry.getKey(); + for (EObject eObject : entry.getValue().distinctValues()) { + processor.process(eObject, value); + } + } + } else throw new UnsupportedOperationException("TODO implement"); + } + + public Set getAllDistinctHolders() { + return getHolderToValueMap().keySet(); + } + + public Set getAllDistinctValues() { + return getValueToHolderMap().keySet(); + } + + public Set getDistinctHoldersOfValue(Object value) { + IMultiset holdersMultiset = getValueToHolderMap().get(value); + if (holdersMultiset == null) + return Collections.emptySet(); + else return holdersMultiset.distinctValues(); + } + + + public Set getDistinctValuesOfHolder(EObject holder) { + IMultiset valuesMultiset = getHolderToValueMap().get(holder); + if (valuesMultiset == null) + return Collections.emptySet(); + else return valuesMultiset.distinctValues(); + } + + public boolean isInstance(EObject source, Object target) { + // TODO we currently assume V2H map exists + if (valueToHolderMap != null) { + IMultiset holders = valueToHolderMap.get(target); + return holders != null && holders.containsNonZero(source); + } else throw new UnsupportedOperationException("TODO implement"); + } + + + } + + + FeatureData getFeatureData(Object featureKey) { + FeatureData data = featureDataMap.get(featureKey); + if (data == null) { + data = createFeatureData(featureKey); + featureDataMap.put(featureKey, data); + } + return data; + } + + /** + * TODO: specialize for to-one features and unique to-many features + */ + protected FeatureData createFeatureData(Object featureKey) { + FeatureData data = new FeatureData(); + data.featureKey = featureKey; + return data; + } + + + + + protected void insertFeatureTuple(final Object featureKey, boolean unique, final Object value, final EObject holder) { + boolean changed = getFeatureData(featureKey).insertFeatureTuple(unique, value, holder); + if (changed) { // if not duplicated + + if (valueToFeatureMap != null) { + insertIntoValueToFeatureMap(featureKey, value); + } + + isDirty = true; + navigationHelper.notifyFeatureListeners(holder, featureKey, value, true); + } + } + + protected void removeFeatureTuple(final Object featureKey, boolean unique, final Object value, final EObject holder) { + boolean changed = getFeatureData(featureKey).removeFeatureTuple(unique, value, holder); + if (changed) { // if not duplicated + + if (valueToFeatureMap != null) { + removeFromValueToFeatureMap(featureKey, value); + } + + isDirty = true; + navigationHelper.notifyFeatureListeners(holder, featureKey, value, false); + } + } + + + public Set getFeatureKeysPointingTo(Object target) { + final IMultiset sources = getValueToFeatureMap().get(target); + return sources == null ? Collections.emptySet() : sources.distinctValues(); + } + + protected Map> getValueToFeatureMap() { + if (valueToFeatureMap == null) { // must be inverted from feature data + valueToFeatureMap = CollectionsFactory.createMap(); + for (FeatureData featureData : featureDataMap.values()) { + final Object featureKey = featureData.getFeatureKey(); + featureData.forEach((source, target) -> insertIntoValueToFeatureMap(featureKey, target)); + } + } + return valueToFeatureMap; + } + + protected void insertIntoValueToFeatureMap(final Object featureKey, Object target) { + IMultiset featureKeys = valueToFeatureMap.computeIfAbsent(target, CollectionsFactory::emptyMultiset); + featureKeys.addOne(featureKey); + } + protected void removeFromValueToFeatureMap(final Object featureKey, final Object value) { + IMultiset featureKeys = valueToFeatureMap.get(value); + if (featureKeys == null) + throw new IllegalStateException(); + featureKeys.removeOne(featureKey); + if (featureKeys.isEmpty()) + valueToFeatureMap.remove(value); + } + + // START ********* InstanceSet ********* + public Set getInstanceSet(final Object keyClass) { + return instanceMap.get(keyClass); + } + + public void removeInstanceSet(final Object keyClass) { + instanceMap.remove(keyClass); + } + + public void insertIntoInstanceSet(final Object keyClass, final EObject value) { + Set set = instanceMap.computeIfAbsent(keyClass, CollectionsFactory::emptySet); + + if (!set.add(value)) { + navigationHelper.logIncidentInstanceInsertion(keyClass, value); + } else { + isDirty = true; + navigationHelper.notifyInstanceListeners(keyClass, value, true); + } + } + + public void removeFromInstanceSet(final Object keyClass, final EObject value) { + final Set set = instanceMap.get(keyClass); + if (set != null) { + if(!set.remove(value)) { + navigationHelper.logIncidentInstanceRemoval(keyClass, value); + } else { + if (set.isEmpty()) { + instanceMap.remove(keyClass); + } + isDirty = true; + navigationHelper.notifyInstanceListeners(keyClass, value, false); + } + } else { + navigationHelper.logIncidentInstanceRemoval(keyClass, value); + } + + } + + + + // END ********* InstanceSet ********* + + // START ********* DataTypeMap ********* + public Set getDistinctDataTypeInstances(final Object keyType) { + IMultiset values = dataTypeMap.get(keyType); + return values == null ? Collections.emptySet() : values.distinctValues(); + } + + public void removeDataTypeMap(final Object keyType) { + dataTypeMap.remove(keyType); + } + + public void insertIntoDataTypeMap(final Object keyType, final Object value) { + IMultiset valMap = dataTypeMap.computeIfAbsent(keyType, CollectionsFactory::emptyMultiset); + final boolean firstOccurrence = valMap.addOne(value); + + isDirty = true; + navigationHelper.notifyDataTypeListeners(keyType, value, true, firstOccurrence); + } + + public void removeFromDataTypeMap(final Object keyType, final Object value) { + final IMultiset valMap = dataTypeMap.get(keyType); + if (valMap != null) { + final boolean lastOccurrence = valMap.removeOne(value); + if (lastOccurrence && valMap.isEmpty()) { + dataTypeMap.remove(keyType); + } + + isDirty = true; + navigationHelper.notifyDataTypeListeners(keyType, value, false, lastOccurrence); + } + } + + // END ********* DataTypeMap ********* + + protected Set getHoldersOfFeature(Object featureKey) { + FeatureData featureData = getFeatureData(featureKey); + return featureData.getAllDistinctHolders(); + } + protected Set getValuesOfFeature(Object featureKey) { + FeatureData featureData = getFeatureData(featureKey); + return featureData.getAllDistinctValues(); + } + + /** + * Returns all EClasses that currently have direct instances cached by the index. + *

+ * Supertypes will not be returned, unless they have direct instances in the model as well. If not in + * wildcard mode, only registered EClasses and their subtypes will be returned. + *

+ * Note for advanced users: if a type is represented by multiple EClass objects, one of them is chosen as + * representative and returned. + */ + public Set getAllCurrentClasses() { + final Set result = CollectionsFactory.createSet(); + final Set classifierKeys = instanceMap.keySet(); + for (final Object classifierKey : classifierKeys) { + final EClassifier knownClassifier = navigationHelper.metaStore.getKnownClassifierForKey(classifierKey); + if (knownClassifier instanceof EClass) { + result.add((EClass) knownClassifier); + } + } + return result; + } + + Set getOldValuesForHolderAndFeature(EObject source, Object featureKey) { + // while this is slower than using the holderToFeatureToValueMap, we do not want to construct that to avoid + // memory overhead + Map> oldValuesToHolders = getFeatureData(featureKey).valueToHolderMap; + Set oldValues = new HashSet(); + for (Entry> entry : oldValuesToHolders.entrySet()) { + if (entry.getValue().containsNonZero(source)) { + oldValues.add(entry.getKey()); + } + } + return oldValues; + } + + protected void forgetFeature(Object featureKey) { + FeatureData removed = featureDataMap.remove(featureKey); + if (valueToFeatureMap != null) { + for (Object value : removed.getAllDistinctValues()) { + removeFromValueToFeatureMap(featureKey, value); + } + } + } + + +} diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/EMFBaseIndexMetaStore.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/EMFBaseIndexMetaStore.java new file mode 100644 index 00000000..52ca3a53 --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/EMFBaseIndexMetaStore.java @@ -0,0 +1,380 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Gabor Bergmann, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.base.core; + +import org.eclipse.emf.common.util.Enumerator; +import org.eclipse.emf.ecore.*; +import tools.refinery.viatra.runtime.base.api.BaseIndexOptions; +import tools.refinery.viatra.runtime.base.api.IndexingLevel; +import tools.refinery.viatra.runtime.base.api.InstanceListener; +import tools.refinery.viatra.runtime.base.exception.ViatraBaseException; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType; +import tools.refinery.viatra.runtime.matchers.util.IMemoryView; +import tools.refinery.viatra.runtime.matchers.util.IMultiLookup; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; + +import java.util.*; +import java.util.Map.Entry; + +/** + * Stores the indexed metamodel information. + * + * @author Gabor Bergmann + * @noextend This class is not intended to be subclassed by clients. + */ +public class EMFBaseIndexMetaStore { + + private static final EClass EOBJECT_CLASS = EcorePackage.eINSTANCE.getEObject(); + private final boolean isDynamicModel; + private NavigationHelperImpl navigationHelper; + + /** + * + */ + public EMFBaseIndexMetaStore(final NavigationHelperImpl navigationHelper) { + this.navigationHelper = navigationHelper; + final BaseIndexOptions options = navigationHelper.getBaseIndexOptions(); + this.isDynamicModel = options.isDynamicEMFMode(); + } + + /** + * Supports collision detection and EEnum canonicalization. Used for all EPackages that have types whose instances + * were encountered at least once. + */ + private final Set knownPackages = new HashSet(); + + /** + * Field variable because it is needed for collision detection. Used for all EClasses whose instances were + * encountered at least once. + */ + private final Set knownClassifiers = new HashSet(); + /** + * Field variable because it is needed for collision detection. Used for all EStructuralFeatures whose instances + * were encountered at least once. + */ + private final Set knownFeatures = new HashSet(); + + /** + * (EClass or String ID) -> all subtypes in knownClasses + */ + private final Map> subTypeMap = new HashMap>(); + /** + * (EClass or String ID) -> all supertypes in knownClasses + */ + private final Map> superTypeMap = new HashMap>(); + + /** + * EPacakge NsURI -> EPackage instances; this is instance-level to detect collisions + */ + private final IMultiLookup uniqueIDToPackage = CollectionsFactory.createMultiLookup(Object.class, MemoryType.SETS, Object.class); + + /** + * static maps between metamodel elements and their unique IDs + */ + private final Map uniqueIDFromClassifier = new HashMap(); + private final Map uniqueIDFromTypedElement = new HashMap(); + private final Map uniqueIDFromEnumerator = new HashMap(); + private final IMultiLookup uniqueIDToClassifier = CollectionsFactory.createMultiLookup(Object.class, MemoryType.SETS, Object.class); + private final IMultiLookup uniqueIDToTypedElement = CollectionsFactory.createMultiLookup(Object.class, MemoryType.SETS, Object.class); + private final IMultiLookup uniqueIDToEnumerator = CollectionsFactory.createMultiLookup(Object.class, MemoryType.SETS, Object.class); + private final Map uniqueIDToCanonicalEnumerator = new HashMap(); + + /** + * Map from enum classes generated for {@link EEnum}s to the actual EEnum. + */ + private Map, EEnum> generatedEENumClasses = new HashMap, EEnum>(); + + /** + * @return the eObjectClassKey + */ + public Object getEObjectClassKey() { + if (eObjectClassKey == null) { + eObjectClassKey = toKey(EOBJECT_CLASS); + } + return eObjectClassKey; + } + private Object eObjectClassKey = null; + + protected Object toKey(final EClassifier classifier) { + if (isDynamicModel) { + return toKeyDynamicInternal(classifier); + } else { + maintainMetamodel(classifier); + return classifier; + } + } + + protected String toKeyDynamicInternal(final EClassifier classifier) { + String id = uniqueIDFromClassifier.get(classifier); + if (id == null) { + Preconditions.checkArgument(!classifier.eIsProxy(), + "Classifier %s is an unresolved proxy", classifier); + id = classifier.getEPackage().getNsURI() + "##" + classifier.getName(); + uniqueIDFromClassifier.put(classifier, id); + uniqueIDToClassifier.addPair(id, classifier); + // metamodel maintenance will call back toKey(), but now the ID maps are already filled + maintainMetamodel(classifier); + } + return id; + } + + protected String enumToKeyDynamicInternal(Enumerator enumerator) { + String id = uniqueIDFromEnumerator.get(enumerator); + if (id == null) { + if (enumerator instanceof EEnumLiteral) { + EEnumLiteral enumLiteral = (EEnumLiteral) enumerator; + final EEnum eEnum = enumLiteral.getEEnum(); + maintainMetamodel(eEnum); + + id = constructEnumID(eEnum.getEPackage().getNsURI(), eEnum.getName(), enumLiteral.getLiteral()); + + // there might be a generated enum for this enum literal! + // generated enum should pre-empt the ecore enum literal as canonical enumerator + Enumerator instanceEnum = enumLiteral.getInstance(); + if (instanceEnum != null && !uniqueIDToCanonicalEnumerator.containsKey(id)) { + uniqueIDToCanonicalEnumerator.put(id, instanceEnum); + } + // if generated enum not found... delay selection of canonical enumerator + } else { // generated enum + final EEnum eEnum = generatedEENumClasses.get(enumerator.getClass()); + if (eEnum != null) + id = constructEnumID(eEnum.getEPackage().getNsURI(), eEnum.getName(), enumerator.getLiteral()); + else + id = constructEnumID("unkownPackage URI", enumerator.getClass().getSimpleName(), + enumerator.getLiteral()); + + // generated enum should pre-empt the ecore enum literal as canonical enumerator + if (!uniqueIDToCanonicalEnumerator.containsKey(id)) { + uniqueIDToCanonicalEnumerator.put(id, enumerator); + } + } + uniqueIDFromEnumerator.put(enumerator, id); + uniqueIDToEnumerator.addPair(id, enumerator); + } + return id; + } + + protected String constructEnumID(String nsURI, String name, String literal) { + return String.format("%s##%s##%s", nsURI, name, literal); + } + + protected Object toKey(final EStructuralFeature feature) { + if (isDynamicModel) { + String id = uniqueIDFromTypedElement.get(feature); + if (id == null) { + Preconditions.checkArgument(!feature.eIsProxy(), + "Element %s is an unresolved proxy", feature); + id = toKeyDynamicInternal((EClassifier) feature.eContainer()) + "##" + feature.getEType().getName() + + "##" + feature.getName(); + uniqueIDFromTypedElement.put(feature, id); + uniqueIDToTypedElement.addPair(id, feature); + // metamodel maintenance will call back toKey(), but now the ID maps are already filled + maintainMetamodel(feature); + } + return id; + } else { + maintainMetamodel(feature); + return feature; + } + } + + protected Enumerator enumToCanonicalDynamicInternal(final Enumerator value) { + final String key = enumToKeyDynamicInternal(value); + Enumerator canonicalEnumerator = uniqueIDToCanonicalEnumerator.computeIfAbsent(key, + // if no canonical version appointed yet, appoint first version + k -> uniqueIDToEnumerator.lookup(k).iterator().next()); + return canonicalEnumerator; + } + + /** + * If in dynamic EMF mode, substitutes enum literals with a canonical version of the enum literal. + */ + protected Object toInternalValueRepresentation(final Object value) { + if (isDynamicModel) { + if (value instanceof Enumerator) + return enumToCanonicalDynamicInternal((Enumerator) value); + else + return value; + } else { + return value; + } + } + + /** + * Checks the {@link EStructuralFeature}'s source and target {@link EPackage} for NsURI collision. An error message + * will be logged if a model element from an other {@link EPackage} instance with the same NsURI has been already + * processed. The error message will be logged only for the first time for a given {@link EPackage} instance. + * + * @param classifier + * the classifier instance + */ + protected void maintainMetamodel(final EStructuralFeature feature) { + if (!knownFeatures.contains(feature)) { + knownFeatures.add(feature); + maintainMetamodel(feature.getEContainingClass()); + maintainMetamodel(feature.getEType()); + } + } + + /** + * put subtype information into cache + */ + protected void maintainMetamodel(final EClassifier classifier) { + if (!knownClassifiers.contains(classifier)) { + checkEPackage(classifier); + knownClassifiers.add(classifier); + + if (classifier instanceof EClass) { + final EClass clazz = (EClass) classifier; + final Object clazzKey = toKey(clazz); + for (final EClass superType : clazz.getEAllSuperTypes()) { + maintainTypeHierarhyInternal(clazzKey, toKey(superType)); + } + maintainTypeHierarhyInternal(clazzKey, getEObjectClassKey()); + } else if (classifier instanceof EEnum) { + EEnum eEnum = (EEnum) classifier; + + if (isDynamicModel) { + // if there is a generated enum class, save this model element for describing that class + if (eEnum.getInstanceClass() != null) + generatedEENumClasses.put(eEnum.getInstanceClass(), eEnum); + + for (EEnumLiteral eEnumLiteral : eEnum.getELiterals()) { + // create string ID; register generated enum values + enumToKeyDynamicInternal(eEnumLiteral); + } + } + } + } + } + + /** + * Checks the {@link EClassifier}'s {@link EPackage} for NsURI collision. An error message will be logged if a model + * element from an other {@link EPackage} instance with the same NsURI has been already processed. The error message + * will be logged only for the first time for a given {@link EPackage} instance. + * + * @param classifier + * the classifier instance + */ + protected void checkEPackage(final EClassifier classifier) { + final EPackage ePackage = classifier.getEPackage(); + if (knownPackages.add(ePackage)) { // this is a new EPackage + final String nsURI = ePackage.getNsURI(); + final IMemoryView packagesOfURI = uniqueIDToPackage.lookupOrEmpty(nsURI); + if (!packagesOfURI.containsNonZero(ePackage)) { // this should be true + uniqueIDToPackage.addPair(nsURI, ePackage); + // collision detection between EPackages (disabled in dynamic model mode) + if (!isDynamicModel && packagesOfURI.size() == 2) { // only report the issue if the new EPackage + // instance is the second for the same URI + navigationHelper.processingError( + new ViatraBaseException("NsURI (" + nsURI + + ") collision detected between different instances of EPackages. If this is normal, try using dynamic EMF mode."), + "process new metamodel elements."); + } + } + } + } + + /** + * Maintains subtype hierarchy + * + * @param subClassKey + * EClass or String id of subclass + * @param superClassKey + * EClass or String id of superclass + */ + protected void maintainTypeHierarhyInternal(final Object subClassKey, final Object superClassKey) { + // update observed class and instance listener tables according to new subtype information + Map allObservedClasses = navigationHelper.getAllObservedClassesInternal(); + if (allObservedClasses.containsKey(superClassKey)) { + // we know that there are no known subtypes of subClassKey at this point, so a single insert should suffice + allObservedClasses.put(subClassKey, allObservedClasses.get(superClassKey)); + } + final Map>> instanceListeners = navigationHelper.peekInstanceListeners(); + if (instanceListeners != null) { // table already constructed + for (final Entry> entry : instanceListeners.getOrDefault(superClassKey, Collections.emptyMap()).entrySet()) { + final InstanceListener listener = entry.getKey(); + for (final EClass subscriptionType : entry.getValue()) { + navigationHelper.addInstanceListenerInternal(listener, subscriptionType, subClassKey); + } + } + } + + // update subtype maps + Set subTypes = subTypeMap.computeIfAbsent(superClassKey, k -> new HashSet<>()); + subTypes.add(subClassKey); + Set superTypes = superTypeMap.computeIfAbsent(subClassKey, k -> new HashSet<>()); + superTypes.add(superClassKey); + } + + /** + * @return the subTypeMap + */ + protected Map> getSubTypeMap() { + return subTypeMap; + } + + protected Map> getSuperTypeMap() { + return superTypeMap; + } + + /** + * Returns the corresponding {@link EStructuralFeature} instance for the id. + * + * @param featureId + * the id of the feature + * @return the {@link EStructuralFeature} instance + */ + public EStructuralFeature getKnownFeature(final String featureId) { + final IMemoryView features = uniqueIDToTypedElement.lookup(featureId); + if (features != null && !features.isEmpty()) { + final ETypedElement next = features.iterator().next(); + if (next instanceof EStructuralFeature) { + return (EStructuralFeature) next; + } + } + return null; + + } + + public EStructuralFeature getKnownFeatureForKey(Object featureKey) { + EStructuralFeature feature; + if (isDynamicModel) { + feature = getKnownFeature((String) featureKey); + } else { + feature = (EStructuralFeature) featureKey; + } + return feature; + } + + /** + * Returns the corresponding {@link EClassifier} instance for the id. + */ + public EClassifier getKnownClassifier(final String key) { + final IMemoryView classifiersOfThisID = uniqueIDToClassifier.lookup(key); + if (classifiersOfThisID != null && !classifiersOfThisID.isEmpty()) { + return classifiersOfThisID.iterator().next(); + } else { + return null; + } + } + + public EClassifier getKnownClassifierForKey(Object classifierKey) { + EClassifier cls; + if (isDynamicModel) { + cls = getKnownClassifier((String) classifierKey); + } else { + cls = (EClassifier) classifierKey; + } + return cls; + } + + +} diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/EMFBaseIndexStatisticsStore.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/EMFBaseIndexStatisticsStore.java new file mode 100644 index 00000000..de66de78 --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/EMFBaseIndexStatisticsStore.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Grill Balázs, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.base.core; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.log4j.Logger; +import org.eclipse.emf.ecore.EClassifier; +import org.eclipse.emf.ecore.EStructuralFeature; + +/** + * @author Grill Balázs + * @noextend This class is not intended to be subclassed by clients. + */ +public class EMFBaseIndexStatisticsStore extends AbstractBaseIndexStore { + + /** + * A common map is used to store instance/value statistics. The key can be an {@link EClassifier}, + * {@link EStructuralFeature} or a String ID. + */ + private final Map stats = new HashMap(); + + public EMFBaseIndexStatisticsStore(NavigationHelperImpl navigationHelper, Logger logger) { + super(navigationHelper, logger); + } + public void addFeature(Object element, Object feature){ + addInstance(feature); + } + + public void removeFeature(Object element, Object feature){ + removeInstance(feature); + } + + public void addInstance(Object key){ + Integer v = stats.get(key); + stats.put(key, v == null ? 1 : v+1); + } + + public void removeInstance(Object key){ + Integer v = stats.get(key); + if(v == null || v <= 0) { + navigationHelper.logIncidentStatRemoval(key); + return; + } + if (v.intValue() == 1){ + stats.remove(key); + }else{ + stats.put(key, v-1); + } + } + public int countInstances(Object key){ + Integer v = stats.get(key); + return v == null ? 0 : v.intValue(); + } + + public void removeType(Object key){ + stats.remove(key); + } + + public int countFeatures(Object feature) { + return countInstances(feature); + } + +} diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/EMFDataSource.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/EMFDataSource.java new file mode 100644 index 00000000..35e590f2 --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/EMFDataSource.java @@ -0,0 +1,137 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Gabor Bergmann, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.base.core; + +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; +import tools.refinery.viatra.runtime.base.api.NavigationHelper; +import tools.refinery.viatra.runtime.base.itc.igraph.IGraphDataSource; +import tools.refinery.viatra.runtime.base.itc.igraph.IGraphObserver; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.IMemoryView; +import tools.refinery.viatra.runtime.matchers.util.IMultiset; + +// TODO IBiDirectionalGraphDataSource +public class EMFDataSource implements IGraphDataSource { + + private List> observers; + private Set references; + private Set classes; + private NavigationHelper navigationHelper; + private IMultiset allEObjects; // contains objects even if only appearing as sources or targets + + /** + * @param navigationHelper + * @param references + * @param classes + * additional classes to treat as nodes. Source and target classes of references need not be added. + */ + public EMFDataSource(NavigationHelper navigationHelper, Set references, Set classes) { + this.references = references; + this.classes = classes; + this.observers = new LinkedList>(); + this.navigationHelper = navigationHelper; + } + + @Override + public Set getAllNodes() { + return getAllEObjects().distinctValues(); + } + + @Override + public IMemoryView getTargetNodes(EObject source) { + IMultiset targetNodes = CollectionsFactory.createMultiset(); + + for (EReference ref : references) { + final Set referenceValues = navigationHelper.getReferenceValues(source, ref); + for (EObject referenceValue : referenceValues) { + targetNodes.addOne(referenceValue); + } + } + + return targetNodes; + } + + @Override + public void attachObserver(IGraphObserver go) { + observers.add(go); + } + + @Override + public void attachAsFirstObserver(IGraphObserver observer) { + observers.add(0, observer); + } + + @Override + public void detachObserver(IGraphObserver go) { + observers.remove(go); + } + + public void notifyEdgeInserted(EObject source, EObject target) { + nodeAdditionInternal(source); + nodeAdditionInternal(target); + for (IGraphObserver o : observers) { + o.edgeInserted(source, target); + } + } + + public void notifyEdgeDeleted(EObject source, EObject target) { + for (IGraphObserver o : observers) { + o.edgeDeleted(source, target); + } + nodeRemovalInternal(source); + nodeRemovalInternal(target); + } + + public void notifyNodeInserted(EObject node) { + nodeAdditionInternal(node); + } + + public void notifyNodeDeleted(EObject node) { + nodeRemovalInternal(node); + } + + private void nodeAdditionInternal(EObject node) { + if (allEObjects.addOne(node)) + for (IGraphObserver o : observers) { + o.nodeInserted(node); + } + } + + private void nodeRemovalInternal(EObject node) { + if (getAllEObjects().removeOne(node)) + for (IGraphObserver o : observers) { + o.nodeDeleted(node); + } + } + + protected IMultiset getAllEObjects() { + if (allEObjects == null) { + allEObjects = CollectionsFactory.createMultiset(); + for (EClass clazz : classes) { + for (EObject obj : navigationHelper.getAllInstances(clazz)) { + allEObjects.addOne(obj); + } + } + for (EReference ref : references) { + navigationHelper.processAllFeatureInstances(ref, (source, target) -> { + allEObjects.addOne(source); + allEObjects.addOne((EObject) target); + }); + } + } + return allEObjects; + } +} diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperContentAdapter.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperContentAdapter.java new file mode 100644 index 00000000..39271c5b --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperContentAdapter.java @@ -0,0 +1,750 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Gabor Bergmann, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Note: this file contains methods copied from EContentAdapter.java of the EMF project + *******************************************************************************/ +package tools.refinery.viatra.runtime.base.core; + +import java.lang.reflect.InvocationTargetException; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.Callable; + +import org.eclipse.emf.common.notify.Adapter; +import org.eclipse.emf.common.notify.Notification; +import org.eclipse.emf.common.notify.Notifier; +import org.eclipse.emf.common.notify.impl.AdapterImpl; +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.emf.ecore.InternalEObject; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.emf.ecore.util.EContentAdapter; +import tools.refinery.viatra.runtime.base.api.BaseIndexOptions; +import tools.refinery.viatra.runtime.base.api.filters.IBaseIndexObjectFilter; +import tools.refinery.viatra.runtime.base.api.filters.IBaseIndexResourceFilter; +import tools.refinery.viatra.runtime.base.comprehension.EMFModelComprehension; +import tools.refinery.viatra.runtime.base.comprehension.EMFVisitor; +import tools.refinery.viatra.runtime.base.core.NavigationHelperVisitor.ChangeVisitor; + +/** + * Content Adapter that recursively attaches itself to the containment hierarchy of an EMF model. + * The purpose is to gather the contents of the model, and to subscribe to model change notifications. + * + *

Originally, this was implemented as a subclass of {@link EContentAdapter}. + * Because of Bug 490105, EContentAdapter is no longer a superclass; its code is copied over with modifications. + * See {@link EContentAdapter} header for original authorship and copyright information. + * + * @author Gabor Bergmann + * @see EContentAdapter + * @noextend This class is not intended to be subclassed by clients. + */ +public class NavigationHelperContentAdapter extends AdapterImpl { + + private final NavigationHelperImpl navigationHelper; + + + + // move optimization to avoid removing and re-adding entire subtrees + EObject ignoreInsertionAndDeletion; + // Set ignoreRootInsertion = new HashSet(); + // Set ignoreRootDeletion = new HashSet(); + + private final EMFModelComprehension comprehension; + + private IBaseIndexObjectFilter objectFilterConfiguration; + private IBaseIndexResourceFilter resourceFilterConfiguration; + + + + private EMFVisitor removalVisitor; + private EMFVisitor insertionVisitor; + + public NavigationHelperContentAdapter(final NavigationHelperImpl navigationHelper) { + this.navigationHelper = navigationHelper; + final BaseIndexOptions options = this.navigationHelper.getBaseIndexOptions(); + objectFilterConfiguration = options.getObjectFilterConfiguration(); + resourceFilterConfiguration = options.getResourceFilterConfiguration(); + this.comprehension = navigationHelper.getComprehension(); + + removalVisitor = initChangeVisitor(false); + insertionVisitor = initChangeVisitor(true); + } + + /** + * Point of customization, called by constructor. + */ + protected ChangeVisitor initChangeVisitor(boolean isInsertion) { + return new NavigationHelperVisitor.ChangeVisitor(navigationHelper, isInsertion); + } + + // key representative of the EObject class + + + @Override + public void notifyChanged(final Notification notification) { + try { + this.navigationHelper.coalesceTraversals(new Callable() { + @Override + public Void call() throws Exception { + simpleNotifyChanged(notification); + + final Object oFeature = notification.getFeature(); + final Object oNotifier = notification.getNotifier(); + if (oNotifier instanceof EObject && oFeature instanceof EStructuralFeature) { + final EObject notifier = (EObject) oNotifier; + final EStructuralFeature feature = (EStructuralFeature) oFeature; + + final boolean notifyLightweightObservers = handleNotification(notification, notifier, feature); + + if (notifyLightweightObservers) { + navigationHelper.notifyLightweightObservers(notifier, feature, notification); + } + } else if (oNotifier instanceof Resource) { + if (notification.getFeatureID(Resource.class) == Resource.RESOURCE__IS_LOADED) { + final Resource resource = (Resource) oNotifier; + if (comprehension.isLoading(resource)) + navigationHelper.resolutionDelayingResources.add(resource); + else + navigationHelper.resolutionDelayingResources.remove(resource); + } + } + return null; + } + }); + } catch (final InvocationTargetException ex) { + navigationHelper.processingFatal(ex.getCause(), "handling the following update notification: " + notification); + } catch (final Exception ex) { + navigationHelper.processingFatal(ex, "handling the following update notification: " + notification); + } + + navigationHelper.notifyBaseIndexChangeListeners(); + } + + @SuppressWarnings("deprecation") + protected boolean handleNotification(final Notification notification, final EObject notifier, + final EStructuralFeature feature) { + final Object oldValue = notification.getOldValue(); + final Object newValue = notification.getNewValue(); + final int positionInt = notification.getPosition(); + final Integer position = positionInt == Notification.NO_INDEX ? null : positionInt; + final int eventType = notification.getEventType(); + boolean notifyLightweightObservers = true; + switch (eventType) { + case Notification.ADD: + featureUpdate(true, notifier, feature, newValue, position); + break; + case Notification.ADD_MANY: + for (final Object newElement : (Collection) newValue) { + featureUpdate(true, notifier, feature, newElement, position); + } + break; + case Notification.CREATE: + notifyLightweightObservers = false; + break; + case Notification.MOVE: + // lightweight observers should be notified on MOVE + break; // currently no support for ordering + case Notification.REMOVE: + featureUpdate(false, notifier, feature, oldValue, position); + break; + case Notification.REMOVE_MANY: + for (final Object oldElement : (Collection) oldValue) { + featureUpdate(false, notifier, feature, oldElement, position); + } + break; + case Notification.REMOVING_ADAPTER: + notifyLightweightObservers = false; + break; + case Notification.RESOLVE: // must be EReference + if (navigationHelper.isFeatureResolveIgnored(feature)) + break; // otherwise same as SET + if (!feature.isMany()) { // if single-valued, can be removed from delayed resolutions + navigationHelper.delayedProxyResolutions.removePairOrNop(notifier, (EReference) feature); + } + featureUpdate(false, notifier, feature, oldValue, position); + featureUpdate(true, notifier, feature, newValue, position); + break; + case Notification.UNSET: + case Notification.SET: + if(feature.isMany() && position == null){ + // spurious UNSET notification of entire collection + notifyLightweightObservers = false; + } else { + featureUpdate(false, notifier, feature, oldValue, position); + featureUpdate(true, notifier, feature, newValue, position); + } + break; + default: + notifyLightweightObservers = false; + break; + } + return notifyLightweightObservers; + } + + protected void featureUpdate(final boolean isInsertion, final EObject notifier, final EStructuralFeature feature, + final Object value, final Integer position) { + // this is a safe visitation, no reads will happen, thus no danger of notifications or matcher construction + comprehension.traverseFeature(getVisitorForChange(isInsertion), notifier, feature, value, position); + } + + // OFFICIAL ENTRY POINT OF BASE INDEX RELATED PARTS + protected void addAdapter(final Notifier notifier) { + if (notifier == ignoreInsertionAndDeletion) { + return; + } + try { + // cross-resource containment workaround, see Bug 483089 and Bug 483086. + if (notifier.eAdapters().contains(this)) + return; + + if (objectFilterConfiguration != null && objectFilterConfiguration.isFiltered(notifier)) { + return; + } + this.navigationHelper.coalesceTraversals(new Callable() { + @Override + public Void call() throws Exception { + // the object is really traversed BEFORE the notification listener is added, + // so that if a proxy is resolved due to the traversal, we do not get notified about it + if (notifier instanceof EObject) { + comprehension.traverseObject(getVisitorForChange(true), (EObject) notifier); + } else if (notifier instanceof Resource) { + Resource resource = (Resource) notifier; + if (resourceFilterConfiguration != null + && resourceFilterConfiguration.isResourceFiltered(resource)) { + return null; + } + if (comprehension.isLoading(resource)) + navigationHelper.resolutionDelayingResources.add(resource); + } + // subscribes to the adapter list, will receive setTarget callback that will spread addAdapter to + // children + simpleAddAdapter(notifier); + return null; + } + }); + } catch (final InvocationTargetException ex) { + navigationHelper.processingFatal(ex.getCause(), "add the object: " + notifier); + } catch (final Exception ex) { + navigationHelper.processingFatal(ex, "add the object: " + notifier); + } + } + + // OFFICIAL ENTRY POINT OF BASE INDEX RELATED PARTS + protected void removeAdapter(final Notifier notifier) { + if (notifier == ignoreInsertionAndDeletion) { + return; + } + try { + removeAdapterInternal(notifier); + } catch (final InvocationTargetException ex) { + navigationHelper.processingFatal(ex.getCause(), "remove the object: " + notifier); + } catch (final Exception ex) { + navigationHelper.processingFatal(ex, "remove the object: " + notifier); + } + } + + // The additional boolean options are there to save the cost of extra checks, see Bug 483089 and Bug 483086. + protected void removeAdapter(final Notifier notifier, boolean additionalObjectContainerPossible, + boolean additionalResourceContainerPossible) { + if (notifier == ignoreInsertionAndDeletion) { + return; + } + try { + + // cross-resource containment workaround, see Bug 483089 and Bug 483086. + if (notifier instanceof InternalEObject) { + InternalEObject internalEObject = (InternalEObject) notifier; + if (additionalResourceContainerPossible) { + Resource eDirectResource = internalEObject.eDirectResource(); + if (eDirectResource != null && eDirectResource.eAdapters().contains(this)) { + return; + } + } + if (additionalObjectContainerPossible) { + InternalEObject eInternalContainer = internalEObject.eInternalContainer(); + if (eInternalContainer != null && eInternalContainer.eAdapters().contains(this)) { + return; + } + } + } + + removeAdapterInternal(notifier); + } catch (final InvocationTargetException ex) { + navigationHelper.processingFatal(ex.getCause(), "remove the object: " + notifier); + } catch (final Exception ex) { + navigationHelper.processingFatal(ex, "remove the object: " + notifier); + } + } + + /** + * @throws InvocationTargetException + */ + protected void removeAdapterInternal(final Notifier notifier) throws InvocationTargetException { + // some non-standard EMF implementations send these + if (!notifier.eAdapters().contains(this)) { + // the adapter was not even attached to the notifier + navigationHelper.logIncidentAdapterRemoval(notifier); + + // skip the rest of the method, do not traverse contents + // as they have either never been added to the index or already removed + return; + } + + if (objectFilterConfiguration != null && objectFilterConfiguration.isFiltered(notifier)) { + return; + } + this.navigationHelper.coalesceTraversals(new Callable() { + @Override + public Void call() throws Exception { + if (notifier instanceof EObject) { + final EObject eObject = (EObject) notifier; + comprehension.traverseObject(getVisitorForChange(false), eObject); + navigationHelper.delayedProxyResolutions.lookupAndRemoveAll(eObject); + } else if (notifier instanceof Resource) { + if (resourceFilterConfiguration != null + && resourceFilterConfiguration.isResourceFiltered((Resource) notifier)) { + return null; + } + navigationHelper.resolutionDelayingResources.remove(notifier); + } + // unsubscribes from the adapter list, will receive unsetTarget callback that will spread + // removeAdapter to children + simpleRemoveAdapter(notifier); + return null; + } + }); + } + + protected EMFVisitor getVisitorForChange(final boolean isInsertion) { + return isInsertion ? insertionVisitor : removalVisitor; + } + + + // WORKAROUND (TMP) for eContents vs. derived features bug + protected void setTarget(final EObject target) { + basicSetTarget(target); + spreadToChildren(target, true); + } + + protected void unsetTarget(final EObject target) { + basicUnsetTarget(target); + spreadToChildren(target, false); + } + + // Spread adapter removal/addition to children of EObject + protected void spreadToChildren(final EObject target, final boolean add) { + final EList features = target.eClass().getEAllReferences(); + for (final EReference feature : features) { + if (!feature.isContainment()) { + continue; + } + if (!comprehension.representable(feature)) { + continue; + } + if (feature.isMany()) { + final Collection values = (Collection) target.eGet(feature); + for (final Object value : values) { + final Notifier notifier = (Notifier) value; + if (add) { + addAdapter(notifier); + } else { + removeAdapter(notifier, false, true); + } + } + } else { + final Object value = target.eGet(feature); + if (value != null) { + final Notifier notifier = (Notifier) value; + if (add) { + addAdapter(notifier); + } else { + removeAdapter(notifier, false, true); + } + } + } + } + } + + + // + // *********************************************************** + // RENAMED METHODS COPIED OVER FROM EContentAdapter DOWN BELOW + // *********************************************************** + // + + /** + * Handles a notification by calling {@link #selfAdapt selfAdapter}. + */ + public void simpleNotifyChanged(Notification notification) + { + selfAdapt(notification); + + super.notifyChanged(notification); + } + + protected void simpleAddAdapter(Notifier notifier) + { + EList eAdapters = notifier.eAdapters(); + if (!eAdapters.contains(this)) + { + eAdapters.add(this); + } + } + + protected void simpleRemoveAdapter(Notifier notifier) + { + notifier.eAdapters().remove(this); + } + + + // + // ********************************************************* + // CODE COPIED OVER VERBATIM FROM EContentAdapter DOWN BELOW + // ********************************************************* + // + + + /** + * Handles a notification by calling {@link #handleContainment handleContainment} + * for any containment-based notification. + */ + protected void selfAdapt(Notification notification) + { + Object notifier = notification.getNotifier(); + if (notifier instanceof ResourceSet) + { + if (notification.getFeatureID(ResourceSet.class) == ResourceSet.RESOURCE_SET__RESOURCES) + { + handleContainment(notification); + } + } + else if (notifier instanceof Resource) + { + if (notification.getFeatureID(Resource.class) == Resource.RESOURCE__CONTENTS) + { + handleContainment(notification); + } + } + else if (notifier instanceof EObject) + { + Object feature = notification.getFeature(); + if (feature instanceof EReference) + { + EReference eReference = (EReference)feature; + if (eReference.isContainment()) + { + handleContainment(notification); + } + } + } + } + + /** + * Handles a containment change by adding and removing the adapter as appropriate. + */ + protected void handleContainment(Notification notification) + { + switch (notification.getEventType()) + { + case Notification.RESOLVE: + { + // We need to be careful that the proxy may be resolved while we are attaching this adapter. + // We need to avoid attaching the adapter during the resolve + // and also attaching it again as we walk the eContents() later. + // Checking here avoids having to check during addAdapter. + // + Notifier oldValue = (Notifier)notification.getOldValue(); + if (oldValue.eAdapters().contains(this)) + { + removeAdapter(oldValue); + Notifier newValue = (Notifier)notification.getNewValue(); + addAdapter(newValue); + } + break; + } + case Notification.UNSET: + { + Object oldValue = notification.getOldValue(); + if (!Objects.equals(oldValue, Boolean.TRUE) && !Objects.equals(oldValue, Boolean.FALSE)) + { + if (oldValue != null) + { + removeAdapter((Notifier)oldValue, false, true); + } + Notifier newValue = (Notifier)notification.getNewValue(); + if (newValue != null) + { + addAdapter(newValue); + } + } + break; + } + case Notification.SET: + { + Notifier oldValue = (Notifier)notification.getOldValue(); + if (oldValue != null) + { + removeAdapter(oldValue, false, true); + } + Notifier newValue = (Notifier)notification.getNewValue(); + if (newValue != null) + { + addAdapter(newValue); + } + break; + } + case Notification.ADD: + { + Notifier newValue = (Notifier)notification.getNewValue(); + if (newValue != null) + { + addAdapter(newValue); + } + break; + } + case Notification.ADD_MANY: + { + @SuppressWarnings("unchecked") Collection newValues = (Collection)notification.getNewValue(); + for (Notifier newValue : newValues) + { + addAdapter(newValue); + } + break; + } + case Notification.REMOVE: + { + Notifier oldValue = (Notifier)notification.getOldValue(); + if (oldValue != null) + { + boolean checkContainer = notification.getNotifier() instanceof Resource; + boolean checkResource = notification.getFeature() != null; + removeAdapter(oldValue, checkContainer, checkResource); + } + break; + } + case Notification.REMOVE_MANY: + { + boolean checkContainer = notification.getNotifier() instanceof Resource; + boolean checkResource = notification.getFeature() != null; + @SuppressWarnings("unchecked") Collection oldValues = (Collection)notification.getOldValue(); + for ( Notifier oldContentValue : oldValues) + { + removeAdapter(oldContentValue, checkContainer, checkResource); + } + break; + } + } + } + + /** + * Handles installation of the adapter + * by adding the adapter to each of the directly contained objects. + */ + @Override + public void setTarget(Notifier target) + { + if (target instanceof EObject) + { + setTarget((EObject)target); + } + else if (target instanceof Resource) + { + setTarget((Resource)target); + } + else if (target instanceof ResourceSet) + { + setTarget((ResourceSet)target); + } + else + { + basicSetTarget(target); + } + } + + /** + * Actually sets the target by calling super. + */ + protected void basicSetTarget(Notifier target) + { + super.setTarget(target); + } + + /** + * Handles installation of the adapter on a Resource + * by adding the adapter to each of the directly contained objects. + */ + protected void setTarget(Resource target) + { + basicSetTarget(target); + List contents = target.getContents(); + for (int i = 0, size = contents.size(); i < size; ++i) + { + Notifier notifier = contents.get(i); + addAdapter(notifier); + } + } + + /** + * Handles installation of the adapter on a ResourceSet + * by adding the adapter to each of the directly contained objects. + */ + protected void setTarget(ResourceSet target) + { + basicSetTarget(target); + List resources = target.getResources(); + for (int i = 0; i < resources.size(); ++i) + { + Notifier notifier = resources.get(i); + addAdapter(notifier); + } + } + + /** + * Handles undoing the installation of the adapter + * by removing the adapter from each of the directly contained objects. + */ + @Override + public void unsetTarget(Notifier target) + { + Object target1 = target; + if (target1 instanceof EObject) + { + unsetTarget((EObject)target1); + } + else if (target1 instanceof Resource) + { + unsetTarget((Resource)target1); + } + else if (target1 instanceof ResourceSet) + { + unsetTarget((ResourceSet)target1); + } + else + { + basicUnsetTarget((Notifier)target1); + } + } + + /** + * Actually unsets the target by calling super. + */ + protected void basicUnsetTarget(Notifier target) + { + super.unsetTarget(target); + } + + /** + * Handles undoing the installation of the adapter from a Resource + * by removing the adapter from each of the directly contained objects. + */ + protected void unsetTarget(Resource target) + { + basicUnsetTarget(target); + List contents = target.getContents(); + for (int i = 0, size = contents.size(); i < size; ++i) + { + Notifier notifier = contents.get(i); + removeAdapter(notifier, true, false); + } + } + + /** + * Handles undoing the installation of the adapter from a ResourceSet + * by removing the adapter from each of the directly contained objects. + */ + protected void unsetTarget(ResourceSet target) + { + basicUnsetTarget(target); + List resources = target.getResources(); + for (int i = 0; i < resources.size(); ++i) + { + Notifier notifier = resources.get(i); + removeAdapter(notifier, false, false); + } + } + + protected boolean resolve() + { + return true; + } + + // + // ********************************************************* + // OBSOLETE CODE COPIED OVER FROM EContentAdapter DOWN BELOW + // ********************************************************* + // + // *** Preserved on purpose as comments, + // *** in order to more easily follow future changes to EContentAdapter. + // + + +// protected void removeAdapter(Notifier notifier, boolean checkContainer, boolean checkResource) +// { +// if (checkContainer || checkResource) +// { +// InternalEObject internalEObject = (InternalEObject) notifier; +// if (checkResource) +// { +// Resource eDirectResource = internalEObject.eDirectResource(); +// if (eDirectResource != null && eDirectResource.eAdapters().contains(this)) +// { +// return; +// } +// } +// if (checkContainer) +// { +// InternalEObject eInternalContainer = internalEObject.eInternalContainer(); +// if (eInternalContainer != null && eInternalContainer.eAdapters().contains(this)) +// { +// return; +// } +// } +// } +// +// removeAdapter(notifier); +// } + +// /** +// * Handles undoing the installation of the adapter from an EObject +// * by removing the adapter from each of the directly contained objects. +// */ +// protected void unsetTarget(EObject target) +// { +// basicUnsetTarget(target); +// for (Iterator i = resolve() ? +// target.eContents().iterator() : +// ((InternalEList)target.eContents()).basicIterator(); +// i.hasNext(); ) +// { +// Notifier notifier = i.next(); +// removeAdapter(notifier, false, true); +// } +// } + +// /** +// * Handles installation of the adapter on an EObject +// * by adding the adapter to each of the directly contained objects. +// */ +// protected void setTarget(EObject target) +// { +// basicSetTarget(target); +// for (Iterator i = resolve() ? +// target.eContents().iterator() : +// ((InternalEList)target.eContents()).basicIterator(); +// i.hasNext(); ) +// { +// Notifier notifier = i.next(); +// addAdapter(notifier); +// } +// } + +} diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperImpl.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperImpl.java new file mode 100644 index 00000000..2b5d74b5 --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperImpl.java @@ -0,0 +1,1702 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Gabor Bergmann, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.base.core; + +import org.apache.log4j.Logger; +import org.eclipse.emf.common.notify.Notification; +import org.eclipse.emf.common.notify.Notifier; +import org.eclipse.emf.common.notify.NotifyingList; +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.*; +import org.eclipse.emf.ecore.EStructuralFeature.Setting; +import org.eclipse.emf.ecore.impl.ENotificationImpl; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.emf.ecore.util.EcoreUtil; +import tools.refinery.viatra.runtime.base.api.*; +import tools.refinery.viatra.runtime.base.api.IEClassifierProcessor.IEClassProcessor; +import tools.refinery.viatra.runtime.base.api.IEClassifierProcessor.IEDataTypeProcessor; +import tools.refinery.viatra.runtime.base.api.filters.IBaseIndexObjectFilter; +import tools.refinery.viatra.runtime.base.api.filters.IBaseIndexResourceFilter; +import tools.refinery.viatra.runtime.base.comprehension.EMFModelComprehension; +import tools.refinery.viatra.runtime.base.comprehension.EMFVisitor; +import tools.refinery.viatra.runtime.base.core.EMFBaseIndexInstanceStore.FeatureData; +import tools.refinery.viatra.runtime.base.core.NavigationHelperVisitor.TraversingVisitor; +import tools.refinery.viatra.runtime.base.core.profiler.ProfilingNavigationHelperContentAdapter; +import tools.refinery.viatra.runtime.base.exception.ViatraBaseException; +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType; +import tools.refinery.viatra.runtime.matchers.util.IMultiLookup; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; + +import java.lang.reflect.InvocationTargetException; +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.Callable; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.util.function.Function.identity; + +/** + * @noextend This class is not intended to be subclassed by clients. + * @author Gabor Bergmann and Tamas Szabo + */ +public class NavigationHelperImpl implements NavigationHelper { + + /** + * This is never null. + */ + protected IndexingLevel wildcardMode; + + + protected Set modelRoots; + private boolean expansionAllowed; + private boolean traversalDescendsAlongCrossResourceContainment; + // protected NavigationHelperVisitor visitor; + protected NavigationHelperContentAdapter contentAdapter; + + protected final Logger logger; + + // type object or String id + protected Map directlyObservedClasses = new HashMap(); + // including subclasses; if null, must be recomputed + protected Map allObservedClasses = null; + protected Map observedDataTypes; + protected Map observedFeatures; + // ignore RESOLVE for these features, as they are just starting to be observed - see [428458] + protected Set ignoreResolveNotificationFeatures; + + /** + * Feature registration and model traversal is delayed while true + */ + protected boolean delayTraversals = false; + /** + * Classes (or String ID in dynamic mode) to be registered once the coalescing period is over + */ + protected Map delayedClasses = new HashMap<>(); + /** + * EStructuralFeatures (or String ID in dynamic mode) to be registered once the coalescing period is over + */ + protected Map delayedFeatures = new HashMap<>(); + /** + * EDataTypes (or String ID in dynamic mode) to be registered once the coalescing period is over + */ + protected Map delayedDataTypes = new HashMap<>(); + + /** + * Features per EObject to be resolved later (towards the end of a coalescing period when no Resources are loading) + */ + protected IMultiLookup delayedProxyResolutions = CollectionsFactory.createMultiLookup(Object.class, MemoryType.SETS, Object.class); + /** + * Reasources that are currently loading, implying the proxy resolution attempts should be delayed + */ + protected Set resolutionDelayingResources = new HashSet(); + + protected Queue traversalCallbacks = new LinkedList(); + + /** + * These global listeners will be called after updates. + */ + // private final Set afterUpdateCallbacks; + private final Set baseIndexChangeListeners; + private final Map> lightweightObservers; + + // These are the user subscriptions to notifications + private final Map> subscribedInstanceListeners; + private final Map> subscribedFeatureListeners; + private final Map> subscribedDataTypeListeners; + + // these are the internal notification tables + // (element Type or String id) -> listener -> (subscription types) + // if null, must be recomputed from subscriptions + // potentially multiple subscription types for each element type because (a) nsURI collisions, (b) multiple + // supertypes + private Map>> instanceListeners; + private Map>> featureListeners; + private Map>> dataTypeListeners; + + private final Set errorListeners; + private final BaseIndexOptions baseIndexOptions; + + private EMFModelComprehension comprehension; + + private boolean loggedRegistrationMessage = false; + + EMFBaseIndexMetaStore metaStore; + EMFBaseIndexInstanceStore instanceStore; + EMFBaseIndexStatisticsStore statsStore; + + Set setMinus(Collection a, Collection b) { + Set result = new HashSet(a); + result.removeAll(b); + return result; + } + + @SuppressWarnings("unchecked") + Set resolveAllInternal(Set a) { + if (a == null) + a = Collections.emptySet(); + Set result = new HashSet(); + for (T t : a) { + if (t.eIsProxy()) { + result.add((T) EcoreUtil.resolve(t, (ResourceSet) null)); + } else { + result.add(t); + } + } + return result; + } + + Set resolveClassifiersToKey(Set classes) { + Set resolveds = resolveAllInternal(classes); + Set result = new HashSet(); + for (EClassifier resolved : resolveds) { + result.add(toKey(resolved)); + } + return result; + } + + Set resolveFeaturesToKey(Set features) { + Set resolveds = resolveAllInternal(features); + Set result = new HashSet(); + for (EStructuralFeature resolved : resolveds) { + result.add(toKey(resolved)); + } + return result; + } + + @Override + public boolean isInWildcardMode() { + return isInWildcardMode(IndexingLevel.FULL); + } + + @Override + public boolean isInWildcardMode(IndexingLevel level) { + return wildcardMode.providesLevel(level); + } + + @Override + public boolean isInDynamicEMFMode() { + return baseIndexOptions.isDynamicEMFMode(); + } + + /** + * @return the baseIndexOptions + */ + public BaseIndexOptions getBaseIndexOptions() { + return baseIndexOptions.copy(); + } + + /** + * @return the comprehension + */ + public EMFModelComprehension getComprehension() { + return comprehension; + } + + /** + * @throws ViatraQueryRuntimeException + */ + public NavigationHelperImpl(Notifier emfRoot, BaseIndexOptions options, Logger logger) { + this.baseIndexOptions = options.copy(); + this.logger = logger; + assert (logger != null); + + this.comprehension = initModelComprehension(); + this.wildcardMode = baseIndexOptions.getWildcardLevel(); + this.subscribedInstanceListeners = new HashMap>(); + this.subscribedFeatureListeners = new HashMap>(); + this.subscribedDataTypeListeners = new HashMap>(); + this.lightweightObservers = CollectionsFactory.createMap(); + this.observedFeatures = new HashMap(); + this.ignoreResolveNotificationFeatures = new HashSet(); + this.observedDataTypes = new HashMap(); + + metaStore = initMetaStore(); + instanceStore = initInstanceStore(); + statsStore = initStatStore(); + + this.contentAdapter = initContentAdapter(); + this.baseIndexChangeListeners = new HashSet(); + this.errorListeners = new LinkedHashSet(); + + this.modelRoots = new HashSet(); + this.expansionAllowed = false; + this.traversalDescendsAlongCrossResourceContainment = false; + + if (emfRoot != null) { + addRootInternal(emfRoot); + } + + } + + @Override + public IndexingLevel getWildcardLevel() { + return wildcardMode; + } + + @Override + public void setWildcardLevel(final IndexingLevel level) { + try{ + IndexingLevel mergedLevel = NavigationHelperImpl.this.wildcardMode.merge(level); + if (mergedLevel != NavigationHelperImpl.this.wildcardMode){ + NavigationHelperImpl.this.wildcardMode = mergedLevel; + + // force traversal upon change of wildcard level + final NavigationHelperVisitor visitor = initTraversingVisitor( + Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap()); + coalesceTraversals(() -> traverse(visitor)); + } + } catch (InvocationTargetException ex) { + processingFatal(ex.getCause(), "Setting wildcard level: " + level); + } catch (Exception ex) { + processingFatal(ex, "Setting wildcard level: " + level); + } + } + + public NavigationHelperContentAdapter getContentAdapter() { + return contentAdapter; + } + + public Map getObservedFeaturesInternal() { + return observedFeatures; + } + + public boolean isFeatureResolveIgnored(EStructuralFeature feature) { + return ignoreResolveNotificationFeatures.contains(toKey(feature)); + } + + @Override + public void dispose() { + ensureNoListenersForDispose(); + for (Notifier root : modelRoots) { + contentAdapter.removeAdapter(root); + } + } + + @Override + public Set getDataTypeInstances(EDataType type) { + Object typeKey = toKey(type); + return Collections.unmodifiableSet(instanceStore.getDistinctDataTypeInstances(typeKey)); + } + + @Override + public boolean isInstanceOfDatatype(Object value, EDataType type) { + Object typeKey = toKey(type); + Set valMap = instanceStore.getDistinctDataTypeInstances(typeKey); + return valMap.contains(value); + } + + protected FeatureData featureData(EStructuralFeature feature) { + return instanceStore.getFeatureData(toKey(feature)); + } + + @Override + public Set findByAttributeValue(Object value_) { + Object value = toCanonicalValueRepresentation(value_); + return getSettingsForTarget(value); + } + + @Override + public Set findByAttributeValue(Object value_, Collection attributes) { + Object value = toCanonicalValueRepresentation(value_); + Set retSet = new HashSet(); + + for (EAttribute attr : attributes) { + for (EObject holder : featureData(attr).getDistinctHoldersOfValue(value)) { + retSet.add(new NavigationHelperSetting(attr, holder, value)); + } + } + + return retSet; + } + + @Override + public Set findByAttributeValue(Object value_, EAttribute attribute) { + Object value = toCanonicalValueRepresentation(value_); + final Set holders = featureData(attribute).getDistinctHoldersOfValue(value); + return Collections.unmodifiableSet(holders); + } + + @Override + public void processAllFeatureInstances(EStructuralFeature feature, IStructuralFeatureInstanceProcessor processor) { + featureData(feature).forEach(processor); + } + + @Override + public void processDirectInstances(EClass type, IEClassProcessor processor) { + Object typeKey = toKey(type); + processDirectInstancesInternal(type, processor, typeKey); + } + + @Override + public void processAllInstances(EClass type, IEClassProcessor processor) { + Object typeKey = toKey(type); + Set subTypes = metaStore.getSubTypeMap().get(typeKey); + if (subTypes != null) { + for (Object subTypeKey : subTypes) { + processDirectInstancesInternal(type, processor, subTypeKey); + } + } + processDirectInstancesInternal(type, processor, typeKey); + } + + @Override + public void processDataTypeInstances(EDataType type, IEDataTypeProcessor processor) { + Object typeKey = toKey(type); + for (Object value : instanceStore.getDistinctDataTypeInstances(typeKey)) { + processor.process(type, value); + } + } + + protected void processDirectInstancesInternal(EClass type, IEClassProcessor processor, Object typeKey) { + final Set instances = instanceStore.getInstanceSet(typeKey); + if (instances != null) { + for (EObject eObject : instances) { + processor.process(type, eObject); + } + } + } + + @Override + public Set getInverseReferences(EObject target) { + return getSettingsForTarget(target); + } + + protected Set getSettingsForTarget(Object target) { + Set retSet = new HashSet(); + for (Object featureKey : instanceStore.getFeatureKeysPointingTo(target)) { + Set holders = instanceStore.getFeatureData(featureKey).getDistinctHoldersOfValue(target); + for (EObject holder : holders) { + EStructuralFeature feature = metaStore.getKnownFeatureForKey(featureKey); + retSet.add(new NavigationHelperSetting(feature, holder, target)); + } + } + return retSet; + } + + @Override + public Set getInverseReferences(EObject target, Collection references) { + Set retSet = new HashSet<>(); + for (EReference ref : references) { + final Set holders = featureData(ref).getDistinctHoldersOfValue(target); + for (EObject source : holders) { + retSet .add(new NavigationHelperSetting(ref, source, target)); + } + } + + return retSet; + } + + @Override + public Set getInverseReferences(EObject target, EReference reference) { + final Set holders = featureData(reference).getDistinctHoldersOfValue(target); + return Collections.unmodifiableSet(holders); + } + + @Override + @SuppressWarnings("unchecked") + public Set getReferenceValues(EObject source, EReference reference) { + Set targets = getFeatureTargets(source, reference); + return (Set) (Set) targets; // this is known to be safe, as EReferences can only point to EObjects + } + + @Override + public Set getFeatureTargets(EObject source, EStructuralFeature _feature) { + return Collections.unmodifiableSet(featureData(_feature).getDistinctValuesOfHolder(source)); + } + + @Override + public boolean isFeatureInstance(EObject source, Object target, EStructuralFeature _feature) { + return featureData(_feature).isInstance(source, target); + } + + @Override + public Set getDirectInstances(EClass type) { + Object typeKey = toKey(type); + Set valSet = instanceStore.getInstanceSet(typeKey); + if (valSet == null) { + return Collections.emptySet(); + } else { + return Collections.unmodifiableSet(valSet); + } + } + + protected Object toKey(EClassifier eClassifier) { + return metaStore.toKey(eClassifier); + } + + protected Object toKey(EStructuralFeature feature) { + return metaStore.toKey(feature); + } + + @Override + public Object toCanonicalValueRepresentation(Object value) { + return metaStore.toInternalValueRepresentation(value); + } + + @Override + public Set getAllInstances(EClass type) { + Set retSet = new HashSet(); + + Object typeKey = toKey(type); + Set subTypes = metaStore.getSubTypeMap().get(typeKey); + if (subTypes != null) { + for (Object subTypeKey : subTypes) { + final Set instances = instanceStore.getInstanceSet(subTypeKey); + if (instances != null) { + retSet.addAll(instances); + } + } + } + final Set instances = instanceStore.getInstanceSet(typeKey); + if (instances != null) { + retSet.addAll(instances); + } + + return retSet; + } + + @Override + public boolean isInstanceOfUnscoped(EObject object, EClass clazz) { + Object candidateTypeKey = toKey(clazz); + Object typeKey = toKey(object.eClass()); + + return doCalculateInstanceOf(candidateTypeKey, typeKey); + } + + @Override + public boolean isInstanceOfScoped(EObject object, EClass clazz) { + Object typeKey = toKey(object.eClass()); + if (!doCalculateInstanceOf(toKey(clazz), typeKey)) { + return false; + } + final Set instances = instanceStore.getInstanceSet(typeKey); + return instances != null && instances.contains(object); + } + + protected boolean doCalculateInstanceOf(Object candidateTypeKey, Object typeKey) { + if (candidateTypeKey.equals(typeKey)) return true; + if (metaStore.getEObjectClassKey().equals(candidateTypeKey)) return true; + + Set superTypes = metaStore.getSuperTypeMap().get(typeKey); + return superTypes.contains(candidateTypeKey); + } + + @Override + public Set findByFeatureValue(Object value_, EStructuralFeature _feature) { + Object value = toCanonicalValueRepresentation(value_); + return Collections.unmodifiableSet(featureData(_feature).getDistinctHoldersOfValue(value)); + } + + @Override + public Set getHoldersOfFeature(EStructuralFeature _feature) { + Object feature = toKey(_feature); + return Collections.unmodifiableSet(instanceStore.getHoldersOfFeature(feature)); + } + @Override + public Set getValuesOfFeature(EStructuralFeature _feature) { + Object feature = toKey(_feature); + return Collections.unmodifiableSet(instanceStore.getValuesOfFeature(feature)); + } + + @Override + public void addInstanceListener(Collection classes, InstanceListener listener) { + Set registered = this.subscribedInstanceListeners.computeIfAbsent(listener, l -> new HashSet<>()); + Set delta = setMinus(classes, registered); + if (!delta.isEmpty()) { + registered.addAll(delta); + if (instanceListeners != null) { // if already computed + for (EClass subscriptionType : delta) { + final Object superElementTypeKey = toKey(subscriptionType); + addInstanceListenerInternal(listener, subscriptionType, superElementTypeKey); + final Set subTypeKeys = metaStore.getSubTypeMap().get(superElementTypeKey); + if (subTypeKeys != null) + for (Object subTypeKey : subTypeKeys) { + addInstanceListenerInternal(listener, subscriptionType, subTypeKey); + } + } + } + } + } + + @Override + public void removeInstanceListener(Collection classes, InstanceListener listener) { + Set restriction = this.subscribedInstanceListeners.get(listener); + if (restriction != null) { + boolean changed = restriction.removeAll(classes); + if (restriction.size() == 0) { + this.subscribedInstanceListeners.remove(listener); + } + if (changed) + instanceListeners = null; // recompute later on demand + } + } + + @Override + public void addFeatureListener(Collection features, FeatureListener listener) { + Set registered = this.subscribedFeatureListeners.computeIfAbsent(listener, l -> new HashSet<>()); + Set delta = setMinus(features, registered); + if (!delta.isEmpty()) { + registered.addAll(delta); + if (featureListeners != null) { // if already computed + for (EStructuralFeature subscriptionType : delta) { + addFeatureListenerInternal(listener, subscriptionType, toKey(subscriptionType)); + } + } + } + } + + @Override + public void removeFeatureListener(Collection features, FeatureListener listener) { + Collection restriction = this.subscribedFeatureListeners.get(listener); + if (restriction != null) { + boolean changed = restriction.removeAll(features); + if (restriction.size() == 0) { + this.subscribedFeatureListeners.remove(listener); + } + if (changed) + featureListeners = null; // recompute later on demand + } + } + + @Override + public void addDataTypeListener(Collection types, DataTypeListener listener) { + Set registered = this.subscribedDataTypeListeners.computeIfAbsent(listener, l -> new HashSet<>()); + Set delta = setMinus(types, registered); + if (!delta.isEmpty()) { + registered.addAll(delta); + if (dataTypeListeners != null) { // if already computed + for (EDataType subscriptionType : delta) { + addDatatypeListenerInternal(listener, subscriptionType, toKey(subscriptionType)); + } + } + } + } + + @Override + public void removeDataTypeListener(Collection types, DataTypeListener listener) { + Collection restriction = this.subscribedDataTypeListeners.get(listener); + if (restriction != null) { + boolean changed = restriction.removeAll(types); + if (restriction.size() == 0) { + this.subscribedDataTypeListeners.remove(listener); + } + if (changed) + dataTypeListeners = null; // recompute later on demand + } + } + + /** + * @return the observedDataTypes + */ + public Map getObservedDataTypesInternal() { + return observedDataTypes; + } + + @Override + public boolean addLightweightEObjectObserver(LightweightEObjectObserver observer, EObject observedObject) { + Set observers = lightweightObservers.computeIfAbsent(observedObject, CollectionsFactory::emptySet); + return observers.add(observer); + } + + @Override + public boolean removeLightweightEObjectObserver(LightweightEObjectObserver observer, EObject observedObject) { + boolean result = false; + Set observers = lightweightObservers.get(observedObject); + if (observers != null) { + result = observers.remove(observer); + if (observers.isEmpty()) { + lightweightObservers.remove(observedObject); + } + } + return result; + } + + public void notifyBaseIndexChangeListeners() { + notifyBaseIndexChangeListeners(instanceStore.isDirty); + if (instanceStore.isDirty) { + instanceStore.isDirty = false; + } + } + + /** + * This will run after updates. + */ + protected void notifyBaseIndexChangeListeners(boolean baseIndexChanged) { + if (!baseIndexChangeListeners.isEmpty()) { + for (EMFBaseIndexChangeListener listener : new ArrayList<>(baseIndexChangeListeners)) { + try { + if (!listener.onlyOnIndexChange() || baseIndexChanged) { + listener.notifyChanged(baseIndexChanged); + } + } catch (Exception ex) { + notifyFatalListener("VIATRA Base encountered an error in delivering notifications about changes. ", + ex); + } + } + } + } + + void notifyDataTypeListeners(final Object typeKey, final Object value, final boolean isInsertion, + final boolean firstOrLastOccurrence) { + for (final Entry> entry : getDataTypeListeners().getOrDefault(typeKey, Collections.emptyMap()).entrySet()) { + final DataTypeListener listener = entry.getKey(); + for (final EDataType subscriptionType : entry.getValue()) { + if (isInsertion) { + listener.dataTypeInstanceInserted(subscriptionType, value, firstOrLastOccurrence); + } else { + listener.dataTypeInstanceDeleted(subscriptionType, value, firstOrLastOccurrence); + } + } + } + } + + void notifyFeatureListeners(final EObject host, final Object featureKey, final Object value, + final boolean isInsertion) { + for (final Entry> entry : getFeatureListeners().getOrDefault(featureKey, Collections.emptyMap()) + .entrySet()) { + final FeatureListener listener = entry.getKey(); + for (final EStructuralFeature subscriptionType : entry.getValue()) { + if (isInsertion) { + listener.featureInserted(host, subscriptionType, value); + } else { + listener.featureDeleted(host, subscriptionType, value); + } + } + } + } + + void notifyInstanceListeners(final Object clazzKey, final EObject instance, final boolean isInsertion) { + for (final Entry> entry : getInstanceListeners().getOrDefault(clazzKey, Collections.emptyMap()).entrySet()) { + final InstanceListener listener = entry.getKey(); + for (final EClass subscriptionType : entry.getValue()) { + if (isInsertion) { + listener.instanceInserted(subscriptionType, instance); + } else { + listener.instanceDeleted(subscriptionType, instance); + } + } + } + } + + void notifyLightweightObservers(final EObject host, final EStructuralFeature feature, + final Notification notification) { + if (lightweightObservers.containsKey(host)) { + Set observers = lightweightObservers.get(host); + for (final LightweightEObjectObserver observer : observers) { + observer.notifyFeatureChanged(host, feature, notification); + } + } + } + + @Override + public void addBaseIndexChangeListener(EMFBaseIndexChangeListener listener) { + Preconditions.checkArgument(listener != null, "Cannot add null listener!"); + baseIndexChangeListeners.add(listener); + } + + @Override + public void removeBaseIndexChangeListener(EMFBaseIndexChangeListener listener) { + Preconditions.checkArgument(listener != null, "Cannot remove null listener!"); + baseIndexChangeListeners.remove(listener); + } + + @Override + public boolean addIndexingErrorListener(IEMFIndexingErrorListener listener) { + return errorListeners.add(listener); + } + + @Override + public boolean removeIndexingErrorListener(IEMFIndexingErrorListener listener) { + return errorListeners.remove(listener); + } + + protected void processingFatal(final Throwable ex, final String task) { + notifyFatalListener(logTaskFormat(task), ex); + } + + protected void processingError(final Throwable ex, final String task) { + notifyErrorListener(logTaskFormat(task), ex); + } + + public void notifyErrorListener(String message, Throwable t) { + logger.error(message, t); + for (IEMFIndexingErrorListener listener : new ArrayList<>(errorListeners)) { + listener.error(message, t); + } + } + + public void notifyFatalListener(String message, Throwable t) { + logger.fatal(message, t); + for (IEMFIndexingErrorListener listener : new ArrayList<>(errorListeners)) { + listener.fatal(message, t); + } + } + + protected String logTaskFormat(final String task) { + return "VIATRA Query encountered an error in processing the EMF model. " + "This happened while trying to " + + task; + } + + protected void considerForExpansion(EObject obj) { + if (expansionAllowed) { + Resource eResource = obj.eResource(); + if (eResource != null && eResource.getResourceSet() == null) { + expandToAdditionalRoot(eResource); + } + } + } + + protected void expandToAdditionalRoot(Notifier root) { + if (modelRoots.contains(root)) + return; + + if (root instanceof ResourceSet) { + expansionAllowed = true; + } else if (root instanceof Resource) { + IBaseIndexResourceFilter resourceFilter = baseIndexOptions.getResourceFilterConfiguration(); + if (resourceFilter != null && resourceFilter.isResourceFiltered((Resource) root)) + return; + } else { // root instanceof EObject + traversalDescendsAlongCrossResourceContainment = true; + } + final IBaseIndexObjectFilter objectFilter = baseIndexOptions.getObjectFilterConfiguration(); + if (objectFilter != null && objectFilter.isFiltered(root)) + return; + + // no veto by filters + modelRoots.add(root); + contentAdapter.addAdapter(root); + notifyBaseIndexChangeListeners(); + } + + /** + * @return the expansionAllowed + */ + public boolean isExpansionAllowed() { + return expansionAllowed; + } + + public boolean traversalDescendsAlongCrossResourceContainment() { + return traversalDescendsAlongCrossResourceContainment; + } + + /** + * @return the directlyObservedClasses + */ + public Set getDirectlyObservedClassesInternal() { + return directlyObservedClasses.keySet(); + } + + boolean isObservedInternal(Object clazzKey) { + return isInWildcardMode() || getAllObservedClassesInternal().containsKey(clazzKey); + } + + /** + * Add the given item the map with the given indexing level if it wasn't already added with a higher level. + * @param level non-null + * @return whether actually changed + */ + protected static boolean putIntoMapMerged(Map map, V key, IndexingLevel level) { + IndexingLevel l = map.get(key); + IndexingLevel merged = level.merge(l); + if (merged != l) { + map.put(key, merged); + return true; + } else { + return false; + } + } + + /** + * @return true if actually changed + */ + protected boolean addObservedClassesInternal(Object eClassKey, IndexingLevel level) { + boolean changed = putIntoMapMerged(allObservedClasses, eClassKey, level); + if (!changed) return false; + + final Set subTypes = metaStore.getSubTypeMap().get(eClassKey); + if (subTypes != null) { + for (Object subType : subTypes) { + /* + * It is necessary to check if the class has already been added with a higher indexing level as in case + * of multiple inheritance, a subclass may be registered for statistics only but full indexing may be + * required via one of its super classes. + */ + putIntoMapMerged(allObservedClasses, subType, level); + } + } + return true; + } + + /** + * not just the directly observed classes, but also their known subtypes + */ + public Map getAllObservedClassesInternal() { + if (allObservedClasses == null) { + allObservedClasses = new HashMap(); + for (Entry entry : directlyObservedClasses.entrySet()) { + Object eClassKey = entry.getKey(); + IndexingLevel level = entry.getValue(); + addObservedClassesInternal(eClassKey, level); + } + } + return allObservedClasses; + } + + /** + * @return the instanceListeners + */ + Map>> getInstanceListeners() { + if (instanceListeners == null) { + instanceListeners = CollectionsFactory.createMap(); + for (Entry> subscription : subscribedInstanceListeners.entrySet()) { + final InstanceListener listener = subscription.getKey(); + for (EClass subscriptionType : subscription.getValue()) { + final Object superElementTypeKey = toKey(subscriptionType); + addInstanceListenerInternal(listener, subscriptionType, superElementTypeKey); + final Set subTypeKeys = metaStore.getSubTypeMap().get(superElementTypeKey); + if (subTypeKeys != null) + for (Object subTypeKey : subTypeKeys) { + addInstanceListenerInternal(listener, subscriptionType, subTypeKey); + } + } + } + } + return instanceListeners; + } + + Map>> peekInstanceListeners() { + return instanceListeners; + } + + void addInstanceListenerInternal(final InstanceListener listener, EClass subscriptionType, + final Object elementTypeKey) { + Set subscriptionTypes = instanceListeners + .computeIfAbsent(elementTypeKey, k -> CollectionsFactory.createMap()) + .computeIfAbsent(listener, k -> CollectionsFactory.createSet()); + subscriptionTypes.add(subscriptionType); + } + + /** + * @return the featureListeners + */ + Map>> getFeatureListeners() { + if (featureListeners == null) { + featureListeners = CollectionsFactory.createMap(); + for (Entry> subscription : subscribedFeatureListeners.entrySet()) { + final FeatureListener listener = subscription.getKey(); + for (EStructuralFeature subscriptionType : subscription.getValue()) { + final Object elementTypeKey = toKey(subscriptionType); + addFeatureListenerInternal(listener, subscriptionType, elementTypeKey); + } + } + } + return featureListeners; + } + + void addFeatureListenerInternal(final FeatureListener listener, EStructuralFeature subscriptionType, + final Object elementTypeKey) { + Set subscriptionTypes = featureListeners + .computeIfAbsent(elementTypeKey, k -> CollectionsFactory.createMap()) + .computeIfAbsent(listener, k -> CollectionsFactory.createSet()); + subscriptionTypes.add(subscriptionType); + } + + /** + * @return the dataTypeListeners + */ + Map>> getDataTypeListeners() { + if (dataTypeListeners == null) { + dataTypeListeners = CollectionsFactory.createMap(); + for (Entry> subscription : subscribedDataTypeListeners.entrySet()) { + final DataTypeListener listener = subscription.getKey(); + for (EDataType subscriptionType : subscription.getValue()) { + final Object elementTypeKey = toKey(subscriptionType); + addDatatypeListenerInternal(listener, subscriptionType, elementTypeKey); + } + } + } + return dataTypeListeners; + } + + void addDatatypeListenerInternal(final DataTypeListener listener, EDataType subscriptionType, + final Object elementTypeKey) { + Set subscriptionTypes = dataTypeListeners + .computeIfAbsent(elementTypeKey, k -> CollectionsFactory.createMap()) + .computeIfAbsent(listener, k -> CollectionsFactory.createSet()); + subscriptionTypes.add(subscriptionType); + } + + public void registerObservedTypes(Set classes, Set dataTypes, + Set features) { + registerObservedTypes(classes, dataTypes, features, IndexingLevel.FULL); + } + + @Override + public void registerObservedTypes(Set classes, Set dataTypes, + Set features, final IndexingLevel level) { + if (isRegistrationNecessary(level) && (classes != null || features != null || dataTypes != null)) { + final Set resolvedFeatures = resolveFeaturesToKey(features); + final Set resolvedClasses = resolveClassifiersToKey(classes); + final Set resolvedDatatypes = resolveClassifiersToKey(dataTypes); + + try { + coalesceTraversals(() -> { + Function f = input -> level; + delayedFeatures.putAll(resolvedFeatures.stream().collect(Collectors.toMap(identity(), f))); + delayedDataTypes.putAll(resolvedDatatypes.stream().collect(Collectors.toMap(identity(), f))); + delayedClasses.putAll(resolvedClasses.stream().collect(Collectors.toMap(identity(), f))); + }); + } catch (InvocationTargetException ex) { + processingFatal(ex.getCause(), "register en masse the observed EClasses " + resolvedClasses + + " and EDatatypes " + resolvedDatatypes + " and EStructuralFeatures " + resolvedFeatures); + } catch (Exception ex) { + processingFatal(ex, "register en masse the observed EClasses " + resolvedClasses + " and EDatatypes " + + resolvedDatatypes + " and EStructuralFeatures " + resolvedFeatures); + } + } + } + + @Override + public void unregisterObservedTypes(Set classes, Set dataTypes, + Set features) { + unregisterEClasses(classes); + unregisterEDataTypes(dataTypes); + unregisterEStructuralFeatures(features); + } + + @Override + public void registerEStructuralFeatures(Set features, final IndexingLevel level) { + if (isRegistrationNecessary(level) && features != null) { + final Set resolved = resolveFeaturesToKey(features); + + try { + coalesceTraversals(() -> resolved.forEach(o -> delayedFeatures.put(o, level))); + } catch (InvocationTargetException ex) { + processingFatal(ex.getCause(), "register the observed EStructuralFeatures: " + resolved); + } catch (Exception ex) { + processingFatal(ex, "register the observed EStructuralFeatures: " + resolved); + } + } + } + + @Override + public void unregisterEStructuralFeatures(Set features) { + if (isRegistrationNecessary(IndexingLevel.FULL) && features != null) { + final Set resolved = resolveFeaturesToKey(features); + ensureNoListeners(resolved, getFeatureListeners()); + observedFeatures.keySet().removeAll(resolved); + delayedFeatures.keySet().removeAll(resolved); + for (Object f : resolved) { + instanceStore.forgetFeature(f); + statsStore.removeType(f); + } + } + } + + @Override + public void registerEClasses(Set classes, final IndexingLevel level) { + if (isRegistrationNecessary(level) && classes != null) { + final Set resolvedClasses = resolveClassifiersToKey(classes); + + try { + coalesceTraversals(() -> resolvedClasses.forEach(o -> delayedClasses.put(o, level))); + } catch (InvocationTargetException ex) { + processingFatal(ex.getCause(), "register the observed EClasses: " + resolvedClasses); + } catch (Exception ex) { + processingFatal(ex, "register the observed EClasses: " + resolvedClasses); + } + } + } + + /** + * @return true if there is an actual change in the transitively computed observation levels, + * warranting an actual traversal + */ + protected boolean startObservingClasses(Map requestedClassObservations) { + boolean warrantsTraversal = false; + getAllObservedClassesInternal(); // pre-populate + for (Entry request : requestedClassObservations.entrySet()) { + if (putIntoMapMerged(directlyObservedClasses, request.getKey(), request.getValue())) { + // maybe already observed for the sake of a supertype? + if (addObservedClassesInternal(request.getKey(), request.getValue())) { + warrantsTraversal = true; + }; + } + } + return warrantsTraversal; + } + + @Override + public void unregisterEClasses(Set classes) { + if (isRegistrationNecessary(IndexingLevel.FULL) && classes != null) { + final Set resolved = resolveClassifiersToKey(classes); + ensureNoListeners(resolved, getInstanceListeners()); + directlyObservedClasses.keySet().removeAll(resolved); + allObservedClasses = null; + delayedClasses.keySet().removeAll(resolved); + for (Object c : resolved) { + instanceStore.removeInstanceSet(c); + statsStore.removeType(c); + } + } + } + + @Override + public void registerEDataTypes(Set dataTypes, final IndexingLevel level) { + if (isRegistrationNecessary(level) && dataTypes != null) { + final Set resolved = resolveClassifiersToKey(dataTypes); + + try { + coalesceTraversals(() -> resolved.forEach(o -> delayedDataTypes.put(o, level))); + } catch (InvocationTargetException ex) { + processingFatal(ex.getCause(), "register the observed EDataTypes: " + resolved); + } catch (Exception ex) { + processingFatal(ex, "register the observed EDataTypes: " + resolved); + } + } + } + + @Override + public void unregisterEDataTypes(Set dataTypes) { + if (isRegistrationNecessary(IndexingLevel.FULL) && dataTypes != null) { + final Set resolved = resolveClassifiersToKey(dataTypes); + ensureNoListeners(resolved, getDataTypeListeners()); + observedDataTypes.keySet().removeAll(resolved); + delayedDataTypes.keySet().removeAll(resolved); + for (Object dataType : resolved) { + instanceStore.removeDataTypeMap(dataType); + statsStore.removeType(dataType); + } + } + } + + @Override + public boolean isCoalescing() { + return delayTraversals; + } + + public void coalesceTraversals(final Runnable runnable) throws InvocationTargetException { + coalesceTraversals(() -> { + runnable.run(); + return null; + }); + } + + @Override + public V coalesceTraversals(Callable callable) throws InvocationTargetException { + V finalResult = null; + + if (delayTraversals) { // reentrant case, no special action needed + try { + finalResult = callable.call(); + } catch (Exception e) { + throw new InvocationTargetException(e); + } + return finalResult; + } + + boolean firstRun = true; + while (callable != null) { // repeat if post-processing needed + + try { + delayTraversals = true; + + V result = callable.call(); + if (firstRun) { + firstRun = false; + finalResult = result; + } + + // are there proxies left to be resolved? are we allowed to resolve them now? + while ((!delayedProxyResolutions.isEmpty()) && resolutionDelayingResources.isEmpty()) { + // pop first entry + EObject toResolveObject = delayedProxyResolutions.distinctKeys().iterator().next(); + EReference toResolveReference = delayedProxyResolutions.lookup(toResolveObject).iterator().next(); + delayedProxyResolutions.removePair(toResolveObject, toResolveReference); + + // see if we can resolve proxies + comprehension.tryResolveReference(toResolveObject, toResolveReference); + } + + delayTraversals = false; + callable = considerRevisit(); + } catch (Exception e) { + // since this is a fatal error, it is OK if delayTraversals remains true, + // hence no need for a try-finally block + + notifyFatalListener( + "VIATRA Base encountered an error while traversing the EMF model to gather new information. ", + e); + throw new InvocationTargetException(e); + } + } + executeTraversalCallbacks(); + return finalResult; + } + + protected Callable considerRevisit() { + // has there been any requests for a retraversal at all? + if (!delayedClasses.isEmpty() || !delayedFeatures.isEmpty() || !delayedDataTypes.isEmpty()) { + // make copies of requested types so that + // (a) original accumulators can be cleaned for the next cycle, also + // (b) to remove entries that are already covered, or + // (c) for the rare case that a coalesced traversal is invoked during visitation, + // e.g. by a derived feature implementation + // initialize the collections empty (but with capacity), fill with new entries + final Map toGatherClasses = new HashMap(delayedClasses.size()); + final Map toGatherFeatures = new HashMap(delayedFeatures.size()); + final Map toGatherDataTypes = new HashMap(delayedDataTypes.size()); + + for (Entry requested : delayedFeatures.entrySet()) { + Object typekey = requested.getKey(); + IndexingLevel old = observedFeatures.get(typekey); + IndexingLevel merged = requested.getValue().merge(old); + if (merged != old) toGatherFeatures.put(typekey, merged); + } + for (Entry requested : delayedClasses.entrySet()) { + Object typekey = requested.getKey(); + IndexingLevel old = directlyObservedClasses.get(typekey); + IndexingLevel merged = requested.getValue().merge(old); + if (merged != old) toGatherClasses.put(typekey, merged); + } + for (Entry requested : delayedDataTypes.entrySet()) { + Object typekey = requested.getKey(); + IndexingLevel old = observedDataTypes.get(typekey); + IndexingLevel merged = requested.getValue().merge(old); + if (merged != old) toGatherDataTypes.put(typekey, merged); + } + + delayedClasses.clear(); + delayedFeatures.clear(); + delayedDataTypes.clear(); + + // check if the filtered request sets are empty + // - could be false alarm if we already observe all of them + if (!toGatherClasses.isEmpty() || !toGatherFeatures.isEmpty() || !toGatherDataTypes.isEmpty()) { + final HashMap oldClasses = new HashMap( + directlyObservedClasses); + + /* Instance indexing would add extra entries to the statistics store, so we have to clean the + * appropriate entries. If no re-traversal is required, it is detected earlier; at this point we + * only have to consider the target indexing level. See bug + * https://bugs.eclipse.org/bugs/show_bug.cgi?id=518356 for more details. + * + * This has to be executed before the old observed types are updated to check whether the indexing level increased. + * + * Technically, the statsStore cleanup seems only necessary for EDataTypes; otherwise everything + * works as expected, but it seems a better idea to do the cleanup for all types in the same way */ + toGatherClasses.forEach((key, value) -> { + IndexingLevel oldIndexingLevel = getIndexingLevel(metaStore.getKnownClassifierForKey(key)); + if (value.hasInstances() && oldIndexingLevel.hasStatistics() && !oldIndexingLevel.hasInstances()) { + statsStore.removeType(key); + } + + }); + toGatherFeatures.forEach((key, value) -> { + IndexingLevel oldIndexingLevel = getIndexingLevel(metaStore.getKnownFeatureForKey(key)); + if (value.hasInstances() && oldIndexingLevel.hasStatistics() && !oldIndexingLevel.hasInstances()) { + statsStore.removeType(key); + } + + }); + toGatherDataTypes.forEach((key, value) -> { + IndexingLevel oldIndexingLevel = getIndexingLevel(metaStore.getKnownClassifierForKey(key)); + if (value.hasInstances() && oldIndexingLevel.hasStatistics() && !oldIndexingLevel.hasInstances()) { + statsStore.removeType(key); + } + + }); + + // Are there new classes to be observed that are not available via superclasses? + // (at sufficient level) + // if yes, model traversal needed + // if not, index can be updated without retraversal + boolean classesWarrantTraversal = startObservingClasses(toGatherClasses); + observedDataTypes.putAll(toGatherDataTypes); + observedFeatures.putAll(toGatherFeatures); + + + // So, is an actual traversal needed, or are we done? + if (classesWarrantTraversal || !toGatherFeatures.isEmpty() || !toGatherDataTypes.isEmpty()) { + // repeat the cycle with this visit + final NavigationHelperVisitor visitor = initTraversingVisitor(toGatherClasses, toGatherFeatures, toGatherDataTypes, oldClasses); + + return new Callable() { + @Override + public V call() throws Exception { + // temporarily ignoring RESOLVE on these features, as they were not observed before + ignoreResolveNotificationFeatures.addAll(toGatherFeatures.keySet()); + try { + traverse(visitor); + } finally { + ignoreResolveNotificationFeatures.removeAll(toGatherFeatures.keySet()); + } + return null; + } + }; + + } + } + } + + return null; // no callable -> no further action + } + + protected void executeTraversalCallbacks() throws InvocationTargetException{ + final Runnable[] callbacks = traversalCallbacks.toArray(new Runnable[traversalCallbacks.size()]); + traversalCallbacks.clear(); + if (callbacks.length > 0){ + coalesceTraversals(() -> Arrays.stream(callbacks).forEach(Runnable::run)); + } + } + + protected void traverse(final NavigationHelperVisitor visitor) { + // Cloning model roots avoids a concurrent modification exception + for (Notifier root : new HashSet(modelRoots)) { + comprehension.traverseModel(visitor, root); + } + notifyBaseIndexChangeListeners(); + } + + /** + * Returns a stream of model roots registered to the navigation helper instance + * @since 2.3 + */ + protected Stream getModelRoots() { + return modelRoots.stream(); + } + + @Override + public void addRoot(Notifier emfRoot) { + addRootInternal(emfRoot); + } + + /** + * Supports removing model roots + *

+ * Note: for now this API is considered experimental thus it is not added to the {@link NavigationHelper} interface. + * @since 2.3 + */ + protected void removeRoot(Notifier root) { + if (!((root instanceof EObject) || (root instanceof Resource) || (root instanceof ResourceSet))) { + throw new ViatraBaseException(ViatraBaseException.INVALID_EMFROOT); + } + + if (!modelRoots.contains(root)) + return; + + if (root instanceof Resource) { + IBaseIndexResourceFilter resourceFilter = getBaseIndexOptions().getResourceFilterConfiguration(); + if (resourceFilter != null && resourceFilter.isResourceFiltered((Resource) root)) + return; + } + final IBaseIndexObjectFilter objectFilter = getBaseIndexOptions().getObjectFilterConfiguration(); + if (objectFilter != null && objectFilter.isFiltered(root)) + return; + + // no veto by filters + modelRoots.remove(root); + contentAdapter.removeAdapter(root); + notifyBaseIndexChangeListeners(); + } + + @Override + public void cheapMoveTo(T element, EList targetContainmentReferenceList) { + if (element.eAdapters().contains(contentAdapter) + && targetContainmentReferenceList instanceof NotifyingList) { + final Object listNotifier = ((NotifyingList) targetContainmentReferenceList).getNotifier(); + if (listNotifier instanceof Notifier && ((Notifier) listNotifier).eAdapters().contains(contentAdapter)) { + contentAdapter.ignoreInsertionAndDeletion = element; + try { + targetContainmentReferenceList.add(element); + } finally { + contentAdapter.ignoreInsertionAndDeletion = null; + } + } else { + targetContainmentReferenceList.add(element); + } + } else { + targetContainmentReferenceList.add(element); + } + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public void cheapMoveTo(EObject element, EObject parent, EReference containmentFeature) { + metaStore.maintainMetamodel(containmentFeature); + if (containmentFeature.isMany()) + cheapMoveTo(element, (EList) parent.eGet(containmentFeature)); + else if (element.eAdapters().contains(contentAdapter) && parent.eAdapters().contains(contentAdapter)) { + contentAdapter.ignoreInsertionAndDeletion = element; + try { + parent.eSet(containmentFeature, element); + } finally { + contentAdapter.ignoreInsertionAndDeletion = null; + } + } else { + parent.eSet(containmentFeature, element); + } + } + + protected void addRootInternal(Notifier emfRoot) { + if (!((emfRoot instanceof EObject) || (emfRoot instanceof Resource) || (emfRoot instanceof ResourceSet))) { + throw new ViatraBaseException(ViatraBaseException.INVALID_EMFROOT); + } + expandToAdditionalRoot(emfRoot); + } + + @Override + public Set getAllCurrentClasses() { + return instanceStore.getAllCurrentClasses(); + } + + protected boolean isRegistrationNecessary(IndexingLevel level) { + boolean inWildcardMode = isInWildcardMode(level); + if (inWildcardMode && !loggedRegistrationMessage) { + loggedRegistrationMessage = true; + logger.warn("Type registration/unregistration not required in wildcard mode. This message will not be repeated for future occurences."); + } + return !inWildcardMode; + } + + protected void ensureNoListeners(Set unobservedTypes, + final Map>> listenerRegistry) { + if (!Collections.disjoint(unobservedTypes, listenerRegistry.keySet())) + throw new IllegalStateException("Cannot unregister observed types for which there are active listeners"); + } + + protected void ensureNoListenersForDispose() { + if (!(baseIndexChangeListeners.isEmpty() && subscribedFeatureListeners.isEmpty() + && subscribedDataTypeListeners.isEmpty() && subscribedInstanceListeners.isEmpty())) + throw new IllegalStateException("Cannot dispose while there are active listeners"); + } + + /** + * Resamples the values of not well-behaving derived features if those features are also indexed. + */ + @Override + public void resampleDerivedFeatures() { + // otherwise notifications are delivered anyway + if (!baseIndexOptions.isTraverseOnlyWellBehavingDerivedFeatures()) { + // get all required classes + Set allCurrentClasses = instanceStore.getAllCurrentClasses(); + Set featuresToSample = new HashSet<>(); + // collect features to sample + for (EClass cls : allCurrentClasses) { + EList features = cls.getEAllStructuralFeatures(); + for (EStructuralFeature f : features) { + // is feature only sampled? + if (comprehension.onlySamplingFeature(f)) { + featuresToSample.add(f); + } + } + } + + final EMFVisitor removalVisitor = contentAdapter.getVisitorForChange(false); + final EMFVisitor insertionVisitor = contentAdapter.getVisitorForChange(true); + + // iterate on instances + for (final EStructuralFeature f : featuresToSample) { + EClass containingClass = f.getEContainingClass(); + processAllInstances(containingClass, (type, instance) -> + resampleFeatureValueForHolder(instance, f, insertionVisitor, removalVisitor)); + } + notifyBaseIndexChangeListeners(); + } + } + + protected void resampleFeatureValueForHolder(EObject source, EStructuralFeature feature, + EMFVisitor insertionVisitor, EMFVisitor removalVisitor) { + // traverse features and update value + Object newValue = source.eGet(feature); + Set oldValues = instanceStore.getOldValuesForHolderAndFeature(source, toKey(feature)); + if (feature.isMany()) { + resampleManyFeatureValueForHolder(source, feature, newValue, oldValues, insertionVisitor, removalVisitor); + } else { + resampleSingleFeatureValueForHolder(source, feature, newValue, oldValues, insertionVisitor, removalVisitor); + } + + } + + protected void resampleManyFeatureValueForHolder(EObject source, EStructuralFeature feature, Object newValue, + Set oldValues, EMFVisitor insertionVisitor, EMFVisitor removalVisitor) { + InternalEObject internalEObject = (InternalEObject) source; + Collection newValues = (Collection) newValue; + // add those that are in new but not in old + Set newValueSet = new HashSet(newValues); + newValueSet.removeAll(oldValues); + // remove those that are in old but not in new + oldValues.removeAll(newValues); + if (!oldValues.isEmpty()) { + for (Object ov : oldValues) { + comprehension.traverseFeature(removalVisitor, source, feature, ov, null); + } + ENotificationImpl removeNotification = new ENotificationImpl(internalEObject, Notification.REMOVE_MANY, + feature, oldValues, null); + notifyLightweightObservers(source, feature, removeNotification); + } + if (!newValueSet.isEmpty()) { + for (Object nv : newValueSet) { + comprehension.traverseFeature(insertionVisitor, source, feature, nv, null); + } + ENotificationImpl addNotification = new ENotificationImpl(internalEObject, Notification.ADD_MANY, feature, + null, newValueSet); + notifyLightweightObservers(source, feature, addNotification); + } + } + + protected void resampleSingleFeatureValueForHolder(EObject source, EStructuralFeature feature, Object newValue, + Set oldValues, EMFVisitor insertionVisitor, EMFVisitor removalVisitor) { + InternalEObject internalEObject = (InternalEObject) source; + Object oldValue = oldValues.stream().findFirst().orElse(null); + if (!Objects.equals(oldValue, newValue)) { + // value changed + comprehension.traverseFeature(removalVisitor, source, feature, oldValue, null); + comprehension.traverseFeature(insertionVisitor, source, feature, newValue, null); + ENotificationImpl notification = new ENotificationImpl(internalEObject, Notification.SET, feature, oldValue, + newValue); + notifyLightweightObservers(source, feature, notification); + } + } + + @Override + public int countAllInstances(EClass type) { + int result = 0; + + Object typeKey = toKey(type); + Set subTypes = metaStore.getSubTypeMap().get(typeKey); + if (subTypes != null) { + for (Object subTypeKey : subTypes) { + result += statsStore.countInstances(subTypeKey); + } + } + result += statsStore.countInstances(typeKey); + + return result; + } + + @Override + public int countDataTypeInstances(EDataType dataType) { + return statsStore.countInstances(toKey(dataType)); + } + + @Override + public int countFeatureTargets(EObject seedSource, EStructuralFeature feature) { + return featureData(feature).getDistinctValuesOfHolder(seedSource).size(); + } + + @Override + public int countFeatures(EStructuralFeature feature) { + return statsStore.countFeatures(toKey(feature)); + } + + protected IndexingLevel getIndexingLevel(Object type) { + if (type instanceof EClass) { + return getIndexingLevel((EClass)type); + } else if (type instanceof EDataType) { + return getIndexingLevel((EDataType)type); + } else if (type instanceof EStructuralFeature) { + return getIndexingLevel((EStructuralFeature)type); + } else { + throw new IllegalArgumentException("Unexpected type descriptor " + type.toString()); + } + } + + @Override + public IndexingLevel getIndexingLevel(EClass type) { + Object key = toKey(type); + IndexingLevel level = directlyObservedClasses.get(key); + if (level == null) { + level = delayedClasses.get(key); + } + // Wildcard mode is never null + return wildcardMode.merge(level); + } + + @Override + public IndexingLevel getIndexingLevel(EDataType type) { + Object key = toKey(type); + IndexingLevel level = observedDataTypes.get(key); + if (level == null) { + level = delayedDataTypes.get(key); + } + // Wildcard mode is never null + return wildcardMode.merge(level); + } + + @Override + public IndexingLevel getIndexingLevel(EStructuralFeature feature) { + Object key = toKey(feature); + IndexingLevel level = observedFeatures.get(key); + if (level == null) { + level = delayedFeatures.get(key); + } + // Wildcard mode is never null + return wildcardMode.merge(level); + } + + @Override + public void executeAfterTraversal(final Runnable traversalCallback) throws InvocationTargetException { + coalesceTraversals(() -> traversalCallbacks.add(traversalCallback)); + } + + /** + * Records a non-exception incident such as faulty notifications. + * Depending on the strictness setting {@link BaseIndexOptions#isStrictNotificationMode()} and log levels, + * this may be treated as a fatal error, merely logged, or just ignored. + * + * @param msgProvider message supplier that only invoked if the message actually gets logged. + * + * @since 2.3 + */ + protected void logIncident(Supplier msgProvider) { + if (baseIndexOptions.isStrictNotificationMode()) { + // This will cause e.g. query engine to become tainted + String msg = msgProvider.get(); + notifyFatalListener(msg, new IllegalStateException(msg)); + } else { + if (notificationErrorReported) { + if (logger.isDebugEnabled()) { + String msg = msgProvider.get(); + logger.debug(msg); + } + } else { + notificationErrorReported = true; + String msg = msgProvider.get(); + logger.error(msg); + } + } + } + boolean notificationErrorReported = false; + + +// DESIGNATED CUSTOMIZATION POINTS FOR SUBCLASSES + + /** + * Point of customization, called by constructor + * @since 2.3 + */ + protected NavigationHelperContentAdapter initContentAdapter() { + switch (baseIndexOptions.getIndexerProfilerMode()) { + case START_DISABLED: + return new ProfilingNavigationHelperContentAdapter(this, false); + case START_ENABLED: + return new ProfilingNavigationHelperContentAdapter(this, true); + case OFF: + default: + return new NavigationHelperContentAdapter(this); + } + } + + /** + * Point of customization, called by constructor + * @since 2.3 + */ + protected EMFBaseIndexStatisticsStore initStatStore() { + return new EMFBaseIndexStatisticsStore(this, logger); + } + + /** + * Point of customization, called by constructor + * @since 2.3 + */ + protected EMFBaseIndexInstanceStore initInstanceStore() { + return new EMFBaseIndexInstanceStore(this, logger); + } + + /** + * Point of customization, called by constructor + * @since 2.3 + */ + protected EMFBaseIndexMetaStore initMetaStore() { + return new EMFBaseIndexMetaStore(this); + } + + /** + * Point of customization, called by constructor + * @since 2.3 + */ + protected EMFModelComprehension initModelComprehension() { + return new EMFModelComprehension(baseIndexOptions); + } + + /** + * Point of customization, called at runtime + * @since 2.3 + */ + protected TraversingVisitor initTraversingVisitor(final Map toGatherClasses, + final Map toGatherFeatures, final Map toGatherDataTypes, + final Map oldClasses) { + return new NavigationHelperVisitor.TraversingVisitor(this, + toGatherFeatures, toGatherClasses, oldClasses, toGatherDataTypes); + } + + + + /** + * Point of customization, e.g. override to suppress + * @since 2.3 + */ + protected void logIncidentAdapterRemoval(final Notifier notifier) { + logIncident(() -> String.format("Erroneous removal of unattached notification adapter from notifier %s", notifier)); + } + + /** + * Point of customization, e.g. override to suppress + * @since 2.3 + */ + protected void logIncidentFeatureTupleInsertion(final Object value, final EObject holder, Object featureKey) { + logIncident(() -> String.format( + "Error: trying to add duplicate value %s to the unique feature %s of host object %s. This indicates some errors in underlying model representation.", + value, featureKey, holder)); + } + + /** + * Point of customization, e.g. override to suppress + * @since 2.3 + */ + protected void logIncidentFeatureTupleRemoval(final Object value, final EObject holder, Object featureKey) { + logIncident(() -> String.format( + "Error: trying to remove duplicate value %s from the unique feature %s of host object %s. This indicates some errors in underlying model representation.", + value, featureKey, holder)); + } + + /** + * Point of customization, e.g. override to suppress + * @since 2.3 + */ + protected void logIncidentInstanceInsertion(final Object keyClass, final EObject value) { + logIncident(() -> String.format("Notification received to index %s as a %s, but it already exists in the index. This indicates some errors in underlying model representation.", value, keyClass)); + } + + /** + * Point of customization, e.g. override to suppress + * @since 2.3 + */ + protected void logIncidentInstanceRemoval(final Object keyClass, final EObject value) { + logIncident(() -> String.format("Notification received to remove %s as a %s, but it is missing from the index. This indicates some errors in underlying model representation.", value, keyClass)); + } + + /** + * Point of customization, e.g. override to suppress + * @since 2.3 + */ + protected void logIncidentStatRemoval(Object key) { + logIncident(() -> String.format("No instances of %s is registered before calling removeInstance method.", key)); + } + + +} diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperSetting.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperSetting.java new file mode 100644 index 00000000..56556ea8 --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperSetting.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Gabor Bergmann, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.base.core; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.emf.ecore.EStructuralFeature.Setting; + +/** + * EStructuralFeature.Setting implementation for the NavigationHelper. + * + * @author Tamás Szabó + * + */ +public class NavigationHelperSetting implements Setting { + + private EStructuralFeature feature; + private EObject holder; + private Object value; + + public NavigationHelperSetting() { + super(); + } + + public NavigationHelperSetting(EStructuralFeature feature, EObject holder, Object value) { + super(); + this.feature = feature; + this.holder = holder; + this.value = value; + } + + @Override + public EObject getEObject() { + return holder; + } + + @Override + public EStructuralFeature getEStructuralFeature() { + return feature; + } + + @Override + public Object get(boolean resolve) { + return value; + } + + @Override + public void set(Object newValue) { + this.value = newValue; + } + + @Override + public boolean isSet() { + return (value != null); + } + + @Override + public void unset() { + this.value = null; + } + + @Override + public String toString() { + return "feature = " + feature + " holder = " + holder + " value = " + value; + } +} diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperType.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperType.java new file mode 100644 index 00000000..2bab4914 --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperType.java @@ -0,0 +1,14 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Gabor Bergmann, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.base.core; + +public enum NavigationHelperType { + REGISTER, ALL +} diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperVisitor.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperVisitor.java new file mode 100644 index 00000000..b5de8d20 --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperVisitor.java @@ -0,0 +1,441 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Gabor Bergmann, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.base.core; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.eclipse.emf.ecore.EAttribute; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EClassifier; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.util.EcoreUtil; +import tools.refinery.viatra.runtime.base.api.IndexingLevel; +import tools.refinery.viatra.runtime.base.comprehension.EMFModelComprehension; +import tools.refinery.viatra.runtime.base.comprehension.EMFVisitor; + +public abstract class NavigationHelperVisitor extends EMFVisitor { + + /** + * A visitor for processing a single change event. Does not traverse the model. Uses all the observed types. + */ + public static class ChangeVisitor extends NavigationHelperVisitor { + // local copies to save actual state, in case visitor has to be saved for later due unresolvable proxies + private final IndexingLevel wildcardMode; + private final Map allObservedClasses; + private final Map observedDataTypes; + private final Map observedFeatures; + private final Map sampledClasses; + + public ChangeVisitor(NavigationHelperImpl navigationHelper, boolean isInsertion) { + super(navigationHelper, isInsertion, false); + wildcardMode = navigationHelper.getWildcardLevel(); + allObservedClasses = navigationHelper.getAllObservedClassesInternal(); // new HashSet(); + observedDataTypes = navigationHelper.getObservedDataTypesInternal(); // new HashSet(); + observedFeatures = navigationHelper.getObservedFeaturesInternal(); // new HashSet(); + sampledClasses = new HashMap(); + } + + @Override + protected boolean observesClass(Object eClass) { + return wildcardMode.hasInstances() || (IndexingLevel.FULL == allObservedClasses.get(eClass)) || registerSampledClass(eClass); + } + + protected boolean registerSampledClass(Object eClass) { + Boolean classAlreadyChecked = sampledClasses.get(eClass); + if (classAlreadyChecked != null) { + return classAlreadyChecked; + } + boolean isSampledClass = isSampledClass(eClass); + sampledClasses.put(eClass, isSampledClass); + // do not modify observation configuration during traversal + return false; + } + + @Override + protected boolean observesDataType(Object type) { + return wildcardMode.hasInstances() || (IndexingLevel.FULL == observedDataTypes.get(type)); + } + + @Override + protected boolean observesFeature(Object feature) { + return wildcardMode.hasInstances() || (IndexingLevel.FULL == observedFeatures.get(feature)); + } + + @Override + protected boolean countsFeature(Object feature) { + return wildcardMode.hasStatistics() || observedFeatures.containsKey(feature) && observedFeatures.get(feature).hasStatistics(); + } + + @Override + protected boolean countsDataType(Object type) { + return wildcardMode.hasStatistics() || observedDataTypes.containsKey(type) && observedDataTypes.get(type).hasStatistics(); + } + + @Override + protected boolean countsClass(Object eClass) { + return wildcardMode.hasStatistics() || allObservedClasses.containsKey(eClass) && allObservedClasses.get(eClass).hasStatistics(); + } + } + + /** + * A visitor for a single-pass traversal of the whole model, processing only the given types and inserting them. + */ + public static class TraversingVisitor extends NavigationHelperVisitor { + private final IndexingLevel wildcardMode; + Map features; + Map newClasses; + Map oldClasses; // if decends from an old class, no need to add! + Map classObservationMap; // true for a class even if only a supertype is included in classes; + Map dataTypes; + + public TraversingVisitor(NavigationHelperImpl navigationHelper, Map features, Map newClasses, + Map oldClasses, Map dataTypes) { + super(navigationHelper, true, true); + wildcardMode = navigationHelper.getWildcardLevel(); + this.features = features; + this.newClasses = newClasses; + this.oldClasses = oldClasses; + this.classObservationMap = new HashMap(); + this.dataTypes = dataTypes; + } + + protected IndexingLevel getExistingIndexingLevel(Object eClass){ + IndexingLevel result = IndexingLevel.NONE; + result = result.merge(oldClasses.get(eClass)); + result = result.merge(oldClasses.get(metaStore.getEObjectClassKey())); + if (IndexingLevel.FULL == result) return result; + Set superTypes = metaStore.getSuperTypeMap().get(eClass); + if (superTypes != null){ + for(Object superType: superTypes){ + result = result.merge(oldClasses.get(superType)); + if (IndexingLevel.FULL == result) return result; + } + } + return result; + } + + protected IndexingLevel getRequestedIndexingLevel(Object eClass){ + IndexingLevel result = IndexingLevel.NONE; + result = result.merge(newClasses.get(eClass)); + result = result.merge(newClasses.get(metaStore.getEObjectClassKey())); + if (IndexingLevel.FULL == result) return result; + Set superTypes = metaStore.getSuperTypeMap().get(eClass); + if (superTypes != null){ + for(Object superType: superTypes){ + result = result.merge(newClasses.get(superType)); + if (IndexingLevel.FULL == result) return result; + } + } + return result; + } + + protected IndexingLevel getTraversalIndexing(Object eClass){ + IndexingLevel level = classObservationMap.get(eClass); + if (level == null){ + IndexingLevel existing = getExistingIndexingLevel(eClass); + IndexingLevel requested = getRequestedIndexingLevel(eClass); + + // Calculate the type of indexing which needs to be executed to reach requested indexing state + // Considering indexes which are already available + if (existing == requested || existing == IndexingLevel.FULL) return IndexingLevel.NONE; + if (requested == IndexingLevel.FULL) return IndexingLevel.FULL; + if (requested.hasStatistics() == existing.hasStatistics()) return IndexingLevel.NONE; + if (requested.hasStatistics()) return IndexingLevel.STATISTICS; + return IndexingLevel.NONE; + } + return level; + } + + @Override + protected boolean observesClass(Object eClass) { + if (wildcardMode.hasInstances()) { + return true; + } + return IndexingLevel.FULL == getTraversalIndexing(eClass); + } + + @Override + protected boolean countsClass(Object eClass) { + return wildcardMode.hasStatistics() || getTraversalIndexing(eClass).hasStatistics(); + } + + @Override + protected boolean observesDataType(Object type) { + return wildcardMode.hasInstances() || (IndexingLevel.FULL == dataTypes.get(type)); + } + + @Override + protected boolean observesFeature(Object feature) { + return wildcardMode.hasInstances() || (IndexingLevel.FULL == features.get(feature)); + } + + @Override + protected boolean countsDataType(Object type) { + return wildcardMode.hasStatistics() || dataTypes.containsKey(type) && dataTypes.get(type).hasStatistics(); + } + + @Override + protected boolean countsFeature(Object feature) { + return wildcardMode.hasStatistics() || features.containsKey(feature) && features.get(feature).hasStatistics(); + } + + @Override + public boolean avoidTransientContainmentLink(EObject source, EReference reference, EObject targetObject) { + return !targetObject.eAdapters().contains(navigationHelper.contentAdapter); + } + } + + protected NavigationHelperImpl navigationHelper; + boolean isInsertion; + boolean descendHierarchy; + boolean traverseOnlyWellBehavingDerivedFeatures; + EMFBaseIndexInstanceStore instanceStore; + EMFBaseIndexStatisticsStore statsStore; + EMFBaseIndexMetaStore metaStore; + + NavigationHelperVisitor(NavigationHelperImpl navigationHelper, boolean isInsertion, boolean descendHierarchy) { + super(isInsertion /* preOrder iff insertion */); + this.navigationHelper = navigationHelper; + instanceStore = navigationHelper.instanceStore; + metaStore = navigationHelper.metaStore; + statsStore = navigationHelper.statsStore; + this.isInsertion = isInsertion; + this.descendHierarchy = descendHierarchy; + this.traverseOnlyWellBehavingDerivedFeatures = navigationHelper.getBaseIndexOptions() + .isTraverseOnlyWellBehavingDerivedFeatures(); + } + + @Override + public boolean pruneSubtrees(EObject source) { + return !descendHierarchy; + } + + @Override + public boolean pruneSubtrees(Resource source) { + return !descendHierarchy; + } + + @Override + public boolean pruneFeature(EStructuralFeature feature) { + Object featureKey = toKey(feature); + if (observesFeature(featureKey) || countsFeature(featureKey)) { + return false; + } + if (feature instanceof EAttribute){ + Object dataTypeKey = toKey(((EAttribute) feature).getEAttributeType()); + if (observesDataType(dataTypeKey) || countsDataType(dataTypeKey)) { + return false; + } + } + return !(isInsertion && navigationHelper.isExpansionAllowed() && feature instanceof EReference + && !((EReference) feature).isContainment()); + } + + /** + * @param feature + * key of feature (EStructuralFeature or String id) + */ + protected abstract boolean observesFeature(Object feature); + + /** + * @param feature + * key of data type (EDatatype or String id) + */ + protected abstract boolean observesDataType(Object type); + + /** + * @param feature + * key of class (EClass or String id) + */ + protected abstract boolean observesClass(Object eClass); + + protected abstract boolean countsFeature(Object feature); + + protected abstract boolean countsDataType(Object type); + + protected abstract boolean countsClass(Object eClass); + + @Override + public void visitElement(EObject source) { + EClass eClass = source.eClass(); + if (eClass.eIsProxy()) { + eClass = (EClass) EcoreUtil.resolve(eClass, source); + } + + final Object classKey = toKey(eClass); + if (observesClass(classKey)) { + if (isInsertion) { + instanceStore.insertIntoInstanceSet(classKey, source); + } else { + instanceStore.removeFromInstanceSet(classKey, source); + } + } + if (countsClass(classKey)){ + if (isInsertion){ + statsStore.addInstance(classKey); + } else { + statsStore.removeInstance(classKey); + } + } + } + + @Override + public void visitAttribute(EObject source, EAttribute feature, Object target) { + Object featureKey = toKey(feature); + final Object eAttributeType = toKey(feature.getEAttributeType()); + Object internalValueRepresentation = null; + if (observesFeature(featureKey)) { + // if (internalValueRepresentation == null) // always true + internalValueRepresentation = metaStore.toInternalValueRepresentation(target); + boolean unique = feature.isUnique(); + if (isInsertion) { + instanceStore.insertFeatureTuple(featureKey, unique, internalValueRepresentation, source); + } else { + instanceStore.removeFeatureTuple(featureKey, unique, internalValueRepresentation, source); + } + } + if (countsFeature(featureKey)){ + if (isInsertion) { + statsStore.addFeature(source, featureKey); + }else{ + statsStore.removeFeature(source, featureKey); + } + } + if (observesDataType(eAttributeType)) { + if (internalValueRepresentation == null) + internalValueRepresentation = metaStore.toInternalValueRepresentation(target); + if (isInsertion) { + instanceStore.insertIntoDataTypeMap(eAttributeType, internalValueRepresentation); + } else { + instanceStore.removeFromDataTypeMap(eAttributeType, internalValueRepresentation); + } + } + if (countsDataType(eAttributeType)){ + if (isInsertion){ + statsStore.addInstance(eAttributeType); + } else { + statsStore.removeInstance(eAttributeType); + } + } + } + + @Override + public void visitInternalContainment(EObject source, EReference feature, EObject target) { + visitReference(source, feature, target); + } + + @Override + public void visitNonContainmentReference(EObject source, EReference feature, EObject target) { + visitReference(source, feature, target); + if (isInsertion) { + navigationHelper.considerForExpansion(target); + } + } + + protected void visitReference(EObject source, EReference feature, EObject target) { + Object featureKey = toKey(feature); + if (observesFeature(featureKey)) { + boolean unique = feature.isUnique(); + if (isInsertion) { + instanceStore.insertFeatureTuple(featureKey, unique, target, source); + } else { + instanceStore.removeFeatureTuple(featureKey, unique, target, source); + } + } + if (countsFeature(featureKey)){ + if (isInsertion){ + statsStore.addFeature(source, featureKey); + } else { + statsStore.removeFeature(source, featureKey); + } + } + } + + @Override + // do not attempt to resolve proxies referenced from resources that are still being loaded + public boolean attemptProxyResolutions(EObject source, EReference feature) { + // emptyness is checked first to avoid costly resource lookup in most cases + if (navigationHelper.resolutionDelayingResources.isEmpty()) + return true; + else + return ! navigationHelper.resolutionDelayingResources.contains(source.eResource()); + } + + @Override + public void visitProxyReference(EObject source, EReference reference, EObject targetObject, Integer position) { + if (isInsertion) { // only attempt to resolve proxies if they are inserted + // final Object result = source.eGet(reference, true); + // if (reference.isMany()) { + // // no idea which element to get, have to iterate through + // for (EObject touch : (Iterable) result); + // } + if (navigationHelper.isFeatureResolveIgnored(reference)) + return; // skip resolution; would be ignored anyways + if (position != null && reference.isMany() && attemptProxyResolutions(source, reference)) { + // there is added value in doing the resolution now, when we know the position + // this may save an iteration through the EList if successful + @SuppressWarnings("unchecked") + EObject touch = ((java.util.List) source.eGet(reference, true)).get(position); + // if resolution successful, no further action needed + if (!touch.eIsProxy()) + return; + } + // otherwise, attempt resolution later, at the end of the coalesced traversal block + navigationHelper.delayedProxyResolutions.addPairOrNop(source, reference); + } + } + + protected Object toKey(EStructuralFeature feature) { + return metaStore.toKey(feature); + } + + protected Object toKey(EClassifier eClassifier) { + return metaStore.toKey(eClassifier); + } + + /** + * Decides whether the type must be observed in order to allow re-sampling of any of its features. If not + * well-behaving features are traversed and there is such a feature for this class, the class will be registered + * into the navigation helper, which may cause a re-traversal. + * + */ + protected boolean isSampledClass(Object eClass) { + if (!traverseOnlyWellBehavingDerivedFeatures) { + // TODO we could save this reverse lookup if the calling method would have the EClass, not just the key + EClass knownClass = (EClass) metaStore.getKnownClassifierForKey(eClass); + // check features that are traversed, and whether there is any that must be sampled + for (EStructuralFeature feature : knownClass.getEAllStructuralFeatures()) { + EMFModelComprehension comprehension = navigationHelper.getComprehension(); + if (comprehension.untraversableDirectly(feature)) + continue; + final boolean visitorPrunes = pruneFeature(feature); + if (visitorPrunes) + continue; + // we found a feature to be visited + if (comprehension.onlySamplingFeature(feature)) { + // we found a feature that must be sampled + navigationHelper.registerEClasses(Collections.singleton(feature.getEContainingClass()), IndexingLevel.FULL); + return true; + } + } + } + return false; + } + + @Override + public boolean descendAlongCrossResourceContainments() { + return this.navigationHelper.traversalDescendsAlongCrossResourceContainment(); + } +} diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/TransitiveClosureHelperImpl.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/TransitiveClosureHelperImpl.java new file mode 100644 index 00000000..552696cb --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/TransitiveClosureHelperImpl.java @@ -0,0 +1,153 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Gabor Bergmann, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.base.core; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.emf.ecore.util.EContentAdapter; +import tools.refinery.viatra.runtime.base.api.FeatureListener; +import tools.refinery.viatra.runtime.base.api.IndexingLevel; +import tools.refinery.viatra.runtime.base.api.InstanceListener; +import tools.refinery.viatra.runtime.base.api.NavigationHelper; +import tools.refinery.viatra.runtime.base.api.TransitiveClosureHelper; +import tools.refinery.viatra.runtime.base.itc.alg.incscc.IncSCCAlg; +import tools.refinery.viatra.runtime.base.itc.alg.misc.IGraphPathFinder; +import tools.refinery.viatra.runtime.base.itc.igraph.ITcObserver; + +/** + * Implementation class for the {@link TransitiveClosureHelper}. + * It uses a {@link NavigationHelper} instance to wrap an EMF model + * and make it suitable for the {@link IncSCCAlg} algorithm. + * + * @author Tamas Szabo + * + */ +public class TransitiveClosureHelperImpl extends EContentAdapter implements TransitiveClosureHelper, + ITcObserver, FeatureListener, InstanceListener { + + private IncSCCAlg sccAlg; + private Set features; + private Set classes; + private EMFDataSource dataSource; + private List> tcObservers; + private NavigationHelper navigationHelper; + private boolean disposeBaseIndexWhenDisposed; + + public TransitiveClosureHelperImpl(final NavigationHelper navigationHelper, boolean disposeBaseIndexWhenDisposed, Set references) { + this.tcObservers = new ArrayList>(); + this.navigationHelper = navigationHelper; + this.disposeBaseIndexWhenDisposed = disposeBaseIndexWhenDisposed; + + //NavigationHelper only accepts Set upon registration + this.features = new HashSet(references); + this.classes = collectEClasses(); + /*this.classes = Collections.emptySet();*/ + if (!navigationHelper.isInWildcardMode()) + navigationHelper.registerObservedTypes(classes, null, features, IndexingLevel.FULL); + + this.navigationHelper.addFeatureListener(features, this); + this.navigationHelper.addInstanceListener(classes, this); + + this.dataSource = new EMFDataSource(navigationHelper, references, classes); + + this.sccAlg = new IncSCCAlg(dataSource); + this.sccAlg.attachObserver(this); + } + + private Set collectEClasses() { + Set classes = new HashSet(); + for (EStructuralFeature ref : features) { + classes.add(ref.getEContainingClass()); + classes.add(((EReference) ref).getEReferenceType()); + } + return classes; + } + + @Override + public void attachObserver(ITcObserver to) { + this.tcObservers.add(to); + } + + @Override + public void detachObserver(ITcObserver to) { + this.tcObservers.remove(to); + } + + @Override + public Set getAllReachableTargets(EObject source) { + return this.sccAlg.getAllReachableTargets(source); + } + + @Override + public Set getAllReachableSources(EObject target) { + return this.sccAlg.getAllReachableSources(target); + } + + @Override + public boolean isReachable(EObject source, EObject target) { + return this.sccAlg.isReachable(source, target); + } + + @Override + public void tupleInserted(EObject source, EObject target) { + for (ITcObserver to : tcObservers) { + to.tupleInserted(source, target); + } + } + + @Override + public void tupleDeleted(EObject source, EObject target) { + for (ITcObserver to : tcObservers) { + to.tupleDeleted(source, target); + } + } + + @Override + public void dispose() { + this.sccAlg.dispose(); + this.navigationHelper.removeInstanceListener(classes, this); + this.navigationHelper.removeFeatureListener(features, this); + + if (disposeBaseIndexWhenDisposed) + this.navigationHelper.dispose(); + } + + @Override + public void featureInserted(EObject host, EStructuralFeature feature, Object value) { + this.dataSource.notifyEdgeInserted(host, (EObject) value); + } + + @Override + public void featureDeleted(EObject host, EStructuralFeature feature, Object value) { + this.dataSource.notifyEdgeDeleted(host, (EObject) value); + } + + @Override + public void instanceInserted(EClass clazz, EObject instance) { + this.dataSource.notifyNodeInserted(instance); + } + + @Override + public void instanceDeleted(EClass clazz, EObject instance) { + this.dataSource.notifyNodeDeleted(instance); + } + + @Override + public IGraphPathFinder getPathFinder() { + return this.sccAlg.getPathFinder(); + } +} diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/profiler/ProfilingNavigationHelperContentAdapter.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/profiler/ProfilingNavigationHelperContentAdapter.java new file mode 100644 index 00000000..3ab15430 --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/profiler/ProfilingNavigationHelperContentAdapter.java @@ -0,0 +1,155 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Laszlo Gati, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.base.core.profiler; + +import org.eclipse.emf.common.notify.Notification; +import org.eclipse.emf.common.notify.Notifier; +import tools.refinery.viatra.runtime.base.core.NavigationHelperContentAdapter; +import tools.refinery.viatra.runtime.base.core.NavigationHelperImpl; + +/** + * + * @noinstantiate This class is not intended to be instantiated by clients. + * @noreference This class is not intended to be referenced by clients. + */ +public final class ProfilingNavigationHelperContentAdapter extends NavigationHelperContentAdapter { + + private static class StopWatch { + + private long currentStartTimeNs = 0l; + private long totalElapsedTimeNs = 0l; + private boolean running = false; + + /** + * Puts the timer in running state and saves the current time. + */ + private void start() { + currentStartTimeNs = System.nanoTime(); + running = true; + + } + + /** + * Puts the the timer in stopped state and saves the total time spent in started + * state between the last reset and now + */ + private void stop() { + totalElapsedTimeNs = getTotalElapsedTimeNs(); + running = false; + } + + /** + * @return time between the last start and now + */ + private long getCurrentElapsedTimeNs() { + return System.nanoTime() - currentStartTimeNs; + } + + /** + * @return the total time spent in started state between the last reset and now + */ + private long getTotalElapsedTimeNs() { + return running ? getCurrentElapsedTimeNs() + totalElapsedTimeNs : totalElapsedTimeNs; + } + + /** + * Saves the current time and resets all the time spent between the last reset and now. + */ + private void resetTime() { + currentStartTimeNs = System.currentTimeMillis(); + totalElapsedTimeNs = 0; + } + } + + long notificationCount = 0l; + StopWatch watch = new StopWatch(); + boolean isEnabled = false; + + boolean measurement = false; + + public ProfilingNavigationHelperContentAdapter(NavigationHelperImpl navigationHelper, boolean enabled) { + super(navigationHelper); + this.isEnabled = enabled; + } + + @Override + public void notifyChanged(Notification notification) { + // Handle possibility of reentrancy + if (isEnabled && !measurement) { + try { + measurement = true; + notificationCount++; + watch.start(); + super.notifyChanged(notification); + } finally { + watch.stop(); + measurement = false; + } + } else { + super.notifyChanged(notification); + } + } + + @Override + public void setTarget(Notifier target) { + // Handle possibility of reentrancy + if (isEnabled && !measurement) { + try { + measurement = true; + notificationCount++; + watch.start(); + super.setTarget(target); + } finally { + watch.stop(); + measurement = false; + } + } else { + super.setTarget(target); + } + } + + @Override + public void unsetTarget(Notifier target) { + // Handle possibility of reentrancy + if (isEnabled && !measurement) { + try { + measurement = true; + notificationCount++; + watch.start(); + super.unsetTarget(target); + } finally { + watch.stop(); + measurement = false; + } + } else { + super.unsetTarget(target); + } + } + + public long getNotificationCount() { + return notificationCount; + } + + public long getTotalMeasuredTimeInMS() { + return watch.getTotalElapsedTimeNs() / 1_000_000l; + } + + public boolean isEnabled() { + return isEnabled; + } + + public void setEnabled(boolean isEnabled) { + this.isEnabled = isEnabled; + } + + public void resetMeasurement() { + notificationCount = 0; + watch.resetTime(); + } +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/exception/ViatraBaseException.java b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/exception/ViatraBaseException.java new file mode 100644 index 00000000..fe656c34 --- /dev/null +++ b/subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/exception/ViatraBaseException.java @@ -0,0 +1,25 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Gabor Bergmann, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.base.exception; + +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; + +public class ViatraBaseException extends ViatraQueryRuntimeException { + + private static final long serialVersionUID = -5145445047912938251L; + + public static final String EMPTY_REF_LIST = "At least one EReference must be provided!"; + public static final String INVALID_EMFROOT = "Emf navigation helper can only be attached on the contents of an EMF EObject, Resource, or ResourceSet."; + + public ViatraBaseException(String s) { + super(s); + } + +} diff --git a/subprojects/viatra-runtime-localsearch/about.html b/subprojects/viatra-runtime-localsearch/about.html new file mode 100644 index 00000000..d1d5593a --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/about.html @@ -0,0 +1,26 @@ + + + + +About + + + +

About This Content

+ +

March 18, 2019

+

License

+ +

The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise indicated below, the Content is provided to you under the terms and conditions of the +Eclipse Public License Version 2.0 ("EPL"). A copy of the EPL is available at http://www.eclipse.org/legal/epl-v20.html. +For purposes of the EPL, "Program" will mean the Content.

+ +

If you did not receive this Content directly from the Eclipse Foundation, the Content is being redistributed by another party ("Redistributor") and different terms and conditions may +apply to your use of any object code in the Content. Check the Redistributor's license that was provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise +indicated below, the terms and conditions of the EPL still apply to any source code in the Content and such source code may be obtained at http://www.eclipse.org.

+ + diff --git a/subprojects/viatra-runtime-localsearch/build.gradle.kts b/subprojects/viatra-runtime-localsearch/build.gradle.kts new file mode 100644 index 00000000..2d3886a5 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/build.gradle.kts @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ + +plugins { + id("tools.refinery.gradle.java-library") +} + +dependencies { + implementation(project(":refinery-viatra-runtime")) + implementation(libs.ecore) + implementation(libs.slf4j.log4j) +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/ExecutionLoggerAdapter.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/ExecutionLoggerAdapter.java new file mode 100644 index 00000000..0f7c7b01 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/ExecutionLoggerAdapter.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch; + +import java.util.Optional; +import java.util.function.Consumer; + +import tools.refinery.viatra.runtime.localsearch.matcher.ILocalSearchAdapter; +import tools.refinery.viatra.runtime.localsearch.matcher.LocalSearchMatcher; +import tools.refinery.viatra.runtime.localsearch.operations.IPatternMatcherOperation; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; +import tools.refinery.viatra.runtime.localsearch.operations.check.nobase.ScopeCheck; +import tools.refinery.viatra.runtime.localsearch.plan.SearchPlan; + +/** + * @since 2.0 + */ +public final class ExecutionLoggerAdapter implements ILocalSearchAdapter { + + volatile String indentation = ""; + private final Consumer outputConsumer; + + public ExecutionLoggerAdapter(Consumer outputConsumer) { + this.outputConsumer = outputConsumer; + } + + private void logMessage(String message) { + outputConsumer.accept(message); + } + + private void logMessage(String message, Object...args) { + outputConsumer.accept(String.format(message, args)); + } + + @Override + public void patternMatchingStarted(LocalSearchMatcher lsMatcher) { + logMessage(indentation + "[ START] " + lsMatcher.getQuerySpecification().getFullyQualifiedName()); + } + + @Override + public void noMoreMatchesAvailable(LocalSearchMatcher lsMatcher) { + logMessage(indentation + "[FINISH] " + lsMatcher.getQuerySpecification().getFullyQualifiedName()); + } + + @Override + public void planChanged(Optional oldPlan, Optional newPlan) { + logMessage(indentation + "[ PLAN] " + newPlan.map(p -> p.getSourceBody().getPattern().getFullyQualifiedName()).orElse("")); + logMessage(indentation + newPlan.map(SearchPlan::toString).map(s -> s.replace("\n", "\n" + indentation)).orElse("")); + } + + @Override + public void operationSelected(SearchPlan plan, ISearchOperation operation, MatchingFrame frame, boolean isBacktrack) { + String category = isBacktrack ? "[ BACK] " : "[SELECT] "; + logMessage(indentation + category + operation.toString()); + if (operation instanceof IPatternMatcherOperation) { + indentation = indentation + "\t"; + } + } + + @Override + public void operationExecuted(SearchPlan plan, ISearchOperation operation, MatchingFrame frame, + boolean isSuccessful) { + if (operation instanceof ScopeCheck) return; + if (operation instanceof IPatternMatcherOperation && indentation.length() > 0) { + indentation = indentation.substring(1); + } + logMessage(indentation + "[ %s] %s %s", isSuccessful ? "OK" : "NO", operation.toString(), frame.toString()); + } + + @Override + public void matchFound(SearchPlan plan, MatchingFrame frame) { + logMessage(indentation + "[ MATCH] " + plan.getSourceBody().getPattern().getFullyQualifiedName() + " " + frame.toString()); + } + + @Override + public void duplicateMatchFound(MatchingFrame frame) { + logMessage(indentation + "[ DUPL.] " + frame.toString()); + } +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/MatchingFrame.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/MatchingFrame.java new file mode 100644 index 00000000..9caf32bb --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/MatchingFrame.java @@ -0,0 +1,122 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Akos Horvath, Gergely Varro Zoltan Ujhelyi and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.localsearch; + +import java.util.Arrays; +import java.util.stream.Collectors; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EStructuralFeature; +import tools.refinery.viatra.runtime.matchers.tuple.IModifiableTuple; +import tools.refinery.viatra.runtime.matchers.tuple.VolatileTuple; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; + +/** + * A MatchingFrame is a Volatile Tuple implementation used by the local search engine internally. + */ +public class MatchingFrame extends VolatileTuple implements IModifiableTuple { + + /** + * The array that physically holds the values. + */ + private Object[] frame; + + /** + * @since 1.7 + */ + public MatchingFrame(int frameSize) { + this.frame = new Object[frameSize]; + } + + /** + * Creates a copy of another matching frame; the two frames can be updated separately + * @param other + * @since 1.7 + */ + public MatchingFrame(MatchingFrame other) { + this.frame = Arrays.copyOf(other.frame, other.frame.length); + } + + + + /** + * Returns the value stored inside the matching frame. + * + * @param position + * @return the element stored in the selected position in the frame, or null if it is not yet set + * @throws IndexOutOfBoundsException + * if position is negative + * @throws IllegalArgumentException + * if the position is larger then the length of the frame + */ + public Object getValue(int position) { + Preconditions.checkElementIndex(position, frame.length); + return frame[position]; + } + + /** + * Sets the value of the variable at the given position. For internal use in LS matching only. + * + * @param position the position of the variable within the frame + * @param value the value to be set for the variable + */ + public void setValue(int position, Object value) { + Preconditions.checkElementIndex(position, frame.length); + frame[position] = value; + } + + public boolean testAndSetValue(Integer position, Object value) { + Preconditions.checkElementIndex(position, frame.length); + if (frame[position] == null) { + frame[position] = value; + return true; + } else { + return frame[position].equals(value); + } + } + + @Override + public String toString() { + return Arrays.stream(frame).map(this::stringRepresentation).collect(Collectors.joining(", ", "[", "]")); + } + + private String stringRepresentation(Object obj) { + if (obj == null) { + return "_"; + } else if (obj instanceof EObject) { + EObject eObject = (EObject) obj; + final EStructuralFeature feature = eObject.eClass().getEStructuralFeature("identifier"); + if (feature != null) { + return String.format("%s : %s", eObject.eGet(feature), eObject.eClass().getName()); + } + } + return obj.toString(); + } + + @Override + public int getSize() { + return frame.length; + } + + @Override + public Object get(int index) { + return getValue(index); + } + + @Override + public Object[] getElements() { + return Arrays.copyOf(frame, frame.length); + } + + @Override + public void set(int index, Object value) { + frame[index] = value; + } +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/exceptions/LocalSearchException.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/exceptions/LocalSearchException.java new file mode 100644 index 00000000..c239e766 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/exceptions/LocalSearchException.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Zoltan Ujhelyi, Akos Horvath, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.exceptions; + +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; + +/** + * @author Zoltan Ujhelyi, Akos Horvath + * + */ +public class LocalSearchException extends ViatraQueryRuntimeException { + + private static final long serialVersionUID = -2585896573351435974L; + + public static final String PLAN_EXECUTION_ERROR = "Error while executing search plan"; + public static final String TYPE_ERROR = "Invalid type of variable"; + + public LocalSearchException(String description, Throwable rootException) { + super(description, rootException); + } + + public LocalSearchException(String description) { + super(description); + } + + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/CallWithAdornment.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/CallWithAdornment.java new file mode 100644 index 00000000..0cabeb97 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/CallWithAdornment.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.matcher; + +import java.util.HashSet; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.psystem.IQueryReference; +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; + +/** + * Immutable data that represents the role of a pattern call within an LS query plan. + * + *

The call is expressed as the {@link PConstraint} {@link #call} (implementing {@link IQueryReference}), + * while the stored {@link #adornment} records the way it will be used within a search plan (specifically, + * pattern parameters within the adornment will have their values known at the point of evaluating the constraint). + * + * + * @author Gabor Bergmann + * @since 2.1 + */ +public class CallWithAdornment { + private final IQueryReference call; + private final Set adornment; + + public CallWithAdornment(IQueryReference call, Set adornment) { + this.call = call; + this.adornment = new HashSet<>(adornment); + } + + public IQueryReference getCall() { + return call; + } + + public Set getAdornment() { + return adornment; + } + + + public PQuery getReferredQuery() { + return call.getReferredQuery(); + } + + public MatcherReference getMatcherReference() { + return new MatcherReference(getReferredQuery(), adornment); + } +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/ILocalSearchAdaptable.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/ILocalSearchAdaptable.java new file mode 100644 index 00000000..f4b28ed0 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/ILocalSearchAdaptable.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Peter Lunk, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.matcher; + +import java.util.List; + +/** + * @author Zoltan Ujhelyi + * + */ +public interface ILocalSearchAdaptable { + + List getAdapters(); + + void addAdapter(ILocalSearchAdapter adapter); + + void removeAdapter(ILocalSearchAdapter adapter); + + void removeAdapters(List adapter); + + void addAdapters(List adapter); + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/ILocalSearchAdapter.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/ILocalSearchAdapter.java new file mode 100644 index 00000000..df64b5f1 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/ILocalSearchAdapter.java @@ -0,0 +1,120 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Marton Bur, Akos Horvath, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.matcher; + +import java.util.Optional; + +import tools.refinery.viatra.runtime.localsearch.ExecutionLoggerAdapter; +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; +import tools.refinery.viatra.runtime.localsearch.plan.SearchPlan; +import tools.refinery.viatra.runtime.localsearch.profiler.LocalSearchProfilerAdapter; + + +/** + * A local search adapter allows external code to follow the internal executions of the local search matcher. Possible + * implementations of the interface include profilers and debuggers. + *

+ * EXPERIMENTAL. A few shortcomings have been found for this interface late during the development + * lifecycle of version 2.0 whose solution might need breaking possible future implementors. Because of this, right now + * it is not recommended to provide implementations outside of VIATRA. If necessary, have a look at the built-in + * adapters that should fulfill most cases in the meantime. See bugs https://bugs.eclipse.org/bugs/show_bug.cgi?id=535101 + * and https://bugs.eclipse.org/bugs/show_bug.cgi?id=535102 for details. + * + * @author Marton Bur + * @see ExecutionLoggerAdapter + * @see LocalSearchProfilerAdapter + * + */ +public interface ILocalSearchAdapter { + + /** + * + * @since 1.2 + */ + default void adapterRegistered(ILocalSearchAdaptable adaptable) {}; + /** + * + * @since 1.2 + */ + default void adapterUnregistered(ILocalSearchAdaptable adaptable) {}; + + /** + * Callback method to indicate the start of a matching process + * + * @param lsMatcher the local search matcher that starts the matching + */ + default void patternMatchingStarted(LocalSearchMatcher lsMatcher) {}; + + /** + * Callback method to indicate the end of a matching process + *

+ * WARNING: It is not guaranteed that this method will be called; + * it is possible that a match process will end after a match is found and no other matches are accessed. + * + * @param lsMatcher the local search matcher that finished + * @since 2.0 + */ + default void noMoreMatchesAvailable(LocalSearchMatcher lsMatcher) {}; + + /** + * Callback method to indicate switching to a new plan during the execution of a pattern matching + * + * @param oldPlan the plan that is finished. Value is null when the first plan is starting. + * @param newPlan the plan that will begin execution + * @since 2.0 + */ + default void planChanged(Optional oldPlan, Optional newPlan) {}; + + /** + * Callback method to indicate the selection of an operation to execute + * + * @param plan the current plan executor + * @param frame the current matching frame + * @param isBacktrack if true, the selected operation was reached via backtracking + * @since 2.0 + */ + default void operationSelected(SearchPlan plan, ISearchOperation operation, MatchingFrame frame, boolean isBacktrack) {}; + + /** + * Callback method to indicate that an operation is executed + * + * @param plan the current plan + * @param frame the current matching frame + * @param isSuccessful if true, the operation executed successfully, or false if the execution failed and backtracking will happen + * @since 2.0 + */ + default void operationExecuted(SearchPlan plan, ISearchOperation operation, MatchingFrame frame, boolean isSuccessful) {}; + + /** + * Callback that is used to indicate that a match has been found + * + * @param plan the search plan executor that found the match + * @param frame the frame that holds the substitutions of the variables that match + * @since 2.0 + */ + default void matchFound(SearchPlan plan, MatchingFrame frame) {}; + /** + * Callback that is used to indicate that the previously reported match has been found as a duplicate, thus will be ignored from the match results. + * + * @param plan the search plan executor that found the match + * @param frame the frame that holds the substitutions of the variables that match + * @since 2.0 + */ + default void duplicateMatchFound(MatchingFrame frame) {}; + + /** + * Callback method to indicate that a search plan is initialized in an executor with the given frame and starting operation + * + * @param searchPlan + * @param frame + * @since 2.0 + */ + default void executorInitializing(SearchPlan searchPlan, MatchingFrame frame) {}; +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/ISearchContext.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/ISearchContext.java new file mode 100644 index 00000000..380774bb --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/ISearchContext.java @@ -0,0 +1,143 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.matcher; + +import java.util.Collections; +import java.util.Set; + +import org.apache.log4j.Logger; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EDataType; +import org.eclipse.emf.ecore.EStructuralFeature; +import tools.refinery.viatra.runtime.base.api.IndexingLevel; +import tools.refinery.viatra.runtime.base.api.NavigationHelper; +import tools.refinery.viatra.runtime.localsearch.matcher.integration.IAdornmentProvider; +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; +import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider; +import tools.refinery.viatra.runtime.matchers.backend.ResultProviderRequestor; +import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.util.ICache; +import tools.refinery.viatra.runtime.matchers.util.IProvider; + +/** + * The {@link ISearchContext} interface allows search operations to reuse platform services such as the indexer. + * + * @author Zoltan Ujhelyi + * @noreference This interface is not intended to be referenced by clients. + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + * + */ +public interface ISearchContext { + + /** + * Provides access to the generic query runtime context of the current engine + * @since 1.7 + */ + IQueryRuntimeContext getRuntimeContext(); + + /** + * @param classes + * @param dataTypes + * @param features + */ + void registerObservedTypes(Set classes, Set dataTypes, Set features); + + /** + * Returns a matcher for a selected query specification. + * + * @throws ViatraQueryRuntimeException + * @since 1.5 + */ + IQueryResultProvider getMatcher(CallWithAdornment dependency); + + /** + * Allows search operations to cache values through the entire lifecycle of the local search backend. The values are + * calculated if not cached before using the given provider, or returned from the cache accordingly. + * + * @since 1.7 + */ + T accessBackendLevelCache(Object key, Class clazz, IProvider valueProvider); + + /** + * Returns the engine-specific logger + * + * @since 2.0 + */ + Logger getLogger(); + + /** + * @noreference This class is not intended to be referenced by clients. + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ + public class SearchContext implements ISearchContext { + + private final NavigationHelper navigationHelper; + private final IQueryRuntimeContext runtimeContext; + + private final ICache backendLevelCache; + private final Logger logger; + private final ResultProviderRequestor resultProviderRequestor; + + /** + * Initializes a search context using an arbitrary backend context + */ + public SearchContext(IQueryBackendContext backendContext, ICache backendLevelCache, + ResultProviderRequestor resultProviderRequestor) { + this.resultProviderRequestor = resultProviderRequestor; + this.runtimeContext = backendContext.getRuntimeContext(); + this.logger = backendContext.getLogger(); + this.navigationHelper = null; + + this.backendLevelCache = backendLevelCache; + } + + public void registerObservedTypes(Set classes, Set dataTypes, Set features) { + if (this.navigationHelper.isInWildcardMode()) { + // In wildcard mode, everything is registered (+ register throws an exception) + return; + } + this.navigationHelper.registerObservedTypes(classes, dataTypes, features, IndexingLevel.FULL); + } + + /** + * @throws ViatraQueryRuntimeException + * @since 2.1 + */ + @Override + public IQueryResultProvider getMatcher(CallWithAdornment dependency) { + // Inject adornment for referenced pattern + IAdornmentProvider adornmentProvider = query -> { + if (query.equals(dependency.getReferredQuery())){ + return Collections.singleton(dependency.getAdornment()); + } + return Collections.emptySet(); + }; + return resultProviderRequestor.requestResultProvider(dependency.getCall(), + IAdornmentProvider.toHint(adornmentProvider)); + } + + @Override + public T accessBackendLevelCache(Object key, Class clazz, IProvider valueProvider) { + return backendLevelCache.getValue(key, clazz, valueProvider); + } + + public IQueryRuntimeContext getRuntimeContext() { + return runtimeContext; + } + + @Override + public Logger getLogger() { + return logger; + } + + } +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/LocalSearchMatcher.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/LocalSearchMatcher.java new file mode 100644 index 00000000..e31d7b5c --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/LocalSearchMatcher.java @@ -0,0 +1,301 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Zoltan Ujhelyi, Marton Bur, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.matcher; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.plan.IPlanDescriptor; +import tools.refinery.viatra.runtime.localsearch.plan.SearchPlan; +import tools.refinery.viatra.runtime.localsearch.plan.SearchPlanExecutor; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.tuple.ITuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.VolatileModifiableMaskedTuple; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; + +/** + * @author Zoltan Ujhelyi + * @noinstantiate This class is not intended to be instantiated by clients. + */ +public final class LocalSearchMatcher implements ILocalSearchAdaptable { + + private final List plan; + private final IPlanDescriptor planDescriptor; + private final List adapters; + + /** + * @since 2.0 + */ + public List getPlan() { + return plan; + } + + @Override + public List getAdapters() { + return new ArrayList<>(adapters); + } + + private abstract class PlanExecutionIterator implements Iterator { + + protected final Iterator planIterator; + + protected SearchPlanExecutor currentPlan; + protected MatchingFrame frame; + protected final Set matchSet; + protected VolatileModifiableMaskedTuple parametersOfFrameView; + private boolean isNextMatchCalculated; + + public PlanExecutionIterator(final Iterator planIterator) { + this.planIterator = planIterator; + isNextMatchCalculated = false; + matchSet = new HashSet<>(); + } + + protected boolean selectNextPlan() { + if(currentPlan != null) { + currentPlan.removeAdapters(adapters); + } + boolean validPlanSelected = false; + + SearchPlanExecutor nextPlan = null; + + while (!validPlanSelected && planIterator.hasNext()) { + nextPlan = planIterator.next(); + nextPlan.addAdapters(adapters); + nextPlan.resetPlan(); + + validPlanSelected = initializeMatchingFrame(nextPlan); + } + + if (validPlanSelected) { + for (ILocalSearchAdapter adapter : adapters) { + adapter.planChanged(Optional.ofNullable(currentPlan).map(SearchPlanExecutor::getSearchPlan), + Optional.ofNullable(nextPlan).map(SearchPlanExecutor::getSearchPlan)); + } + currentPlan = nextPlan; + return true; + } else { + currentPlan = null; + return false; + } + } + + protected abstract boolean initializeMatchingFrame(SearchPlanExecutor nextPlan); + + private boolean findNextNewMatchInCurrentPlan() { + boolean foundMatch = currentPlan.execute(frame); + while (foundMatch && matchSet.contains(parametersOfFrameView)) { + for (ILocalSearchAdapter adapter : adapters) { + adapter.duplicateMatchFound(frame); + } + foundMatch = currentPlan.execute(frame); + } + return foundMatch; + } + + @Override + public boolean hasNext() { + if (isNextMatchCalculated) { + return true; + } + if (currentPlan == null) { + return false; + } + boolean foundMatch = findNextNewMatchInCurrentPlan(); + + while (!foundMatch && planIterator.hasNext()) { + foundMatch = selectNextPlan() && findNextNewMatchInCurrentPlan(); + } + if (!foundMatch) { + for (ILocalSearchAdapter adapter : adapters) { + adapter.noMoreMatchesAvailable(LocalSearchMatcher.this); + } + } + isNextMatchCalculated = foundMatch; + return foundMatch; + } + + @Override + public Tuple next() { + if (!hasNext()) { + throw new NoSuchElementException("No more matches available."); + } + isNextMatchCalculated = false; + final Tuple match = parametersOfFrameView.toImmutable(); + matchSet.add(match); + return match; + } + } + + private class PlanExecutionIteratorWithArrayParameters extends PlanExecutionIterator { + + private final Object[] parameterValues; + + public PlanExecutionIteratorWithArrayParameters(Iterator planIterator, final Object[] parameterValues) { + super(planIterator); + this.parameterValues = parameterValues; + selectNextPlan(); + } + + protected boolean initializeMatchingFrame(SearchPlanExecutor nextPlan) { + frame = new MatchingFrame(nextPlan.getVariableMapping().size()); + parametersOfFrameView = new VolatileModifiableMaskedTuple(frame, nextPlan.getParameterMask()); + for (int i = 0; i < parameterValues.length; i++) { + Object valueToSet = parameterValues[i]; + if (valueToSet != null) { + Object oldValue = parametersOfFrameView.get(i); + if (oldValue == null) { + parametersOfFrameView.set(i, valueToSet); + } else if (!Objects.equals(valueToSet, oldValue)) { + // Initial value setting resulted in contradictory values. This can happen because two parameter + // variables have been unified but the call provides different values for the parameters. + return false; + } + // If oldValue is not null but equal to newValue, the setting can be ignored + } + } + + return true; + } + } + private class PlanExecutionIteratorWithTupleParameters extends PlanExecutionIterator { + + private final ITuple parameterValues; + private final TupleMask parameterSeedMask; + + public PlanExecutionIteratorWithTupleParameters(Iterator planIterator, final TupleMask parameterSeedMask, final ITuple parameterValues) { + super(planIterator); + this.parameterSeedMask = parameterSeedMask; + this.parameterValues = parameterValues; + selectNextPlan(); + } + + protected boolean initializeMatchingFrame(SearchPlanExecutor nextPlan) { + frame = new MatchingFrame(nextPlan.getVariableMapping().size()); + parametersOfFrameView = new VolatileModifiableMaskedTuple(frame, nextPlan.getParameterMask()); + for (int i = 0; i < parameterSeedMask.getSize(); i++) { + int index = parameterSeedMask.indices[i]; + Object valueToSet = parameterValues.get(i); + if (valueToSet != null) { + Object oldValue = parametersOfFrameView.get(index); + if (oldValue == null) { + parametersOfFrameView.set(index, valueToSet); + } else if (!Objects.equals(valueToSet, oldValue)) { + // Initial value setting resulted in contradictory values. This can happen because two parameter + // variables have been unified but the call provides different values for the parameters. + return false; + } + // If oldValue is not null but equal to newValue, the setting can be ignored + } + } + + return true; + } + } + + /** + * @since 2.0 + */ + public LocalSearchMatcher(ISearchContext searchContext, IPlanDescriptor planDescriptor, List plan) { + Preconditions.checkArgument(planDescriptor != null, "Cannot initialize matcher with null query."); + this.planDescriptor = planDescriptor; + this.plan = plan.stream().map(p -> new SearchPlanExecutor(p, searchContext)).collect(Collectors.toList()); + this.adapters = new LinkedList<>(); + } + + @Override + public void addAdapter(ILocalSearchAdapter adapter) { + this.adapters.add(adapter); + adapter.adapterRegistered(this); + } + + @Override + public void removeAdapter(ILocalSearchAdapter adapter) { + this.adapters.remove(adapter); + adapter.adapterUnregistered(this); + } + + @Override + public void addAdapters(List adapters) { + this.adapters.addAll(adapters); + for (ILocalSearchAdapter adapter : adapters) { + adapter.adapterRegistered(this); + } + } + + @Override + public void removeAdapters(List adapters) { + this.adapters.removeAll(adapters); + for (ILocalSearchAdapter adapter : adapters) { + adapter.adapterUnregistered(this); + } + } + + public int getParameterCount() { + return planDescriptor.getQuery().getParameters().size(); + } + + private void matchingStarted() { + for (ILocalSearchAdapter adapter : adapters) { + adapter.patternMatchingStarted(this); + } + } + + /** + * @since 2.0 + */ + public Stream streamMatches(final Object[] parameterValues) { + matchingStarted(); + PlanExecutionIterator it = new PlanExecutionIteratorWithArrayParameters(plan.iterator(), parameterValues); + return StreamSupport.stream(Spliterators.spliteratorUnknownSize(it, + Spliterator.IMMUTABLE | Spliterator.NONNULL | Spliterator.DISTINCT), false); + } + + /** + * @since 2.0 + */ + public Stream streamMatches(TupleMask parameterSeedMask, final ITuple parameterValues) { + matchingStarted(); + PlanExecutionIterator it = new PlanExecutionIteratorWithTupleParameters( + plan.iterator(), parameterSeedMask, parameterValues); + return StreamSupport.stream(Spliterators.spliteratorUnknownSize(it, + Spliterator.IMMUTABLE | Spliterator.NONNULL | Spliterator.DISTINCT), false); + } + + /** + * Returns the query specification this matcher used as source for the implementation + * @return never null + */ + public PQuery getQuerySpecification() { + return planDescriptor.getQuery(); + } + + + /** + * @since 1.5 + */ + public IPlanDescriptor getPlanDescriptor() { + return planDescriptor; + } +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/MatcherReference.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/MatcherReference.java new file mode 100644 index 00000000..6bf25192 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/MatcherReference.java @@ -0,0 +1,97 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Zoltan Ujhelyi, Marton Bur, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.matcher; + +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; + +public class MatcherReference { + final PQuery query; + final Set adornment; + + /** + * Hints that can override the callee's own hints. This field is intentionally left out from hashCode and equals + */ + final QueryEvaluationHint hints; + + /** + * @since 1.4 + */ + public MatcherReference(PQuery query, Set adornment, QueryEvaluationHint hints) { + super(); + this.query = query; + this.adornment = adornment; + this.hints = hints; + } + + public MatcherReference(PQuery query, Set adornment){ + this(query, adornment, null); + } + + public PQuery getQuery() { + return query; + } + public Set getAdornment() { + return adornment; + } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + + result = prime * result + ((adornment == null) ? 0 : adornment.hashCode()); + result = prime * result + ((query == null) ? 0 : query.hashCode()); + return result; + } + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + MatcherReference other = (MatcherReference) obj; + if (adornment == null) { + if (other.adornment != null) + return false; + } else if (!adornment.equals(other.adornment)) + return false; + if (query == null) { + if (other.query != null) + return false; + } else if (!query.equals(other.query)) + return false; + return true; + } + + /** + * @return the hints to override the called reference's own hints with. Can be null. + * @since 1.4 + */ + public QueryEvaluationHint getHints() { + return hints; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(query.getFullyQualifiedName()); + sb.append("("); + for(PParameter p : query.getParameters()){ + sb.append(adornment.contains(p) ? "b" : "f"); + } + sb.append(")"); + return sb.toString(); + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/AbstractLocalSearchResultProvider.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/AbstractLocalSearchResultProvider.java new file mode 100644 index 00000000..1ae24d2d --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/AbstractLocalSearchResultProvider.java @@ -0,0 +1,532 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.matcher.integration; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import tools.refinery.viatra.runtime.localsearch.exceptions.LocalSearchException; +import tools.refinery.viatra.runtime.localsearch.matcher.CallWithAdornment; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.matcher.LocalSearchMatcher; +import tools.refinery.viatra.runtime.localsearch.matcher.MatcherReference; +import tools.refinery.viatra.runtime.localsearch.plan.IPlanDescriptor; +import tools.refinery.viatra.runtime.localsearch.plan.IPlanProvider; +import tools.refinery.viatra.runtime.localsearch.plan.SearchPlan; +import tools.refinery.viatra.runtime.localsearch.plan.SearchPlanForBody; +import tools.refinery.viatra.runtime.localsearch.planner.compiler.IOperationCompiler; +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; +import tools.refinery.viatra.runtime.matchers.backend.IMatcherCapability; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackend; +import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider; +import tools.refinery.viatra.runtime.matchers.backend.IUpdateable; +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.backend.QueryHintOption; +import tools.refinery.viatra.runtime.matchers.backend.ResultProviderRequestor; +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.context.IndexingService; +import tools.refinery.viatra.runtime.matchers.planning.QueryProcessingException; +import tools.refinery.viatra.runtime.matchers.planning.helpers.FunctionalDependencyHelper; +import tools.refinery.viatra.runtime.matchers.psystem.IQueryReference; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.PositivePatternCall; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQueries; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.psystem.rewriters.IFlattenCallPredicate; +import tools.refinery.viatra.runtime.matchers.tuple.ITuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.Accuracy; + +/** + * @author Zoltan Ujhelyi + * @since 1.7 + * + */ +public abstract class AbstractLocalSearchResultProvider implements IQueryResultProvider { + + protected final LocalSearchBackend backend; + protected final IQueryBackendContext backendContext; + protected final IQueryRuntimeContext runtimeContext; + protected final PQuery query; + protected final QueryEvaluationHint userHints; + protected final Map hintCache = new HashMap<>(); + protected final IPlanProvider planProvider; + private static final String PLAN_CACHE_KEY = AbstractLocalSearchResultProvider.class.getName() + "#planCache"; + private final Map planCache; + protected final ISearchContext searchContext; + /** + * @since 2.1 + */ + protected ResultProviderRequestor resultProviderRequestor; + + /** + * @since 1.5 + */ + @SuppressWarnings({ "unchecked"}) + public AbstractLocalSearchResultProvider(LocalSearchBackend backend, IQueryBackendContext context, PQuery query, + IPlanProvider planProvider, QueryEvaluationHint userHints) { + this.backend = backend; + this.backendContext = context; + this.query = query; + + this.planProvider = planProvider; + this.userHints = userHints; + this.runtimeContext = context.getRuntimeContext(); + this.resultProviderRequestor = backend.getResultProviderRequestor(query, userHints); + this.searchContext = new ISearchContext.SearchContext(backendContext, backend.getCache(), resultProviderRequestor); + this.planCache = backend.getCache().getValue(PLAN_CACHE_KEY, Map.class, HashMap::new); + } + + protected abstract IOperationCompiler getOperationCompiler(IQueryBackendContext backendContext, LocalSearchHints configuration); + + private IQueryRuntimeContext getRuntimeContext() { + return backend.getRuntimeContext(); + } + + private LocalSearchMatcher createMatcher(IPlanDescriptor plan, final ISearchContext searchContext) { + List executors = plan.getPlan().stream() + .map(input -> new SearchPlan(input.getBody(), input.getCompiledOperations(), input.calculateParameterMask(), + input.getVariableKeys())) + .collect(Collectors.toList()); + return new LocalSearchMatcher(searchContext, plan, executors); + } + + private IPlanDescriptor getOrCreatePlan(MatcherReference key, IQueryBackendContext backendContext, IOperationCompiler compiler, LocalSearchHints configuration, IPlanProvider planProvider) { + if (planCache.containsKey(key)){ + return planCache.get(key); + } else { + IPlanDescriptor plan = planProvider.getPlan(backendContext, compiler, + resultProviderRequestor, configuration, key); + planCache.put(key, plan); + return plan; + } + } + + private IPlanDescriptor getOrCreatePlan(MatcherReference key, IPlanProvider planProvider) { + if (planCache.containsKey(key)){ + return planCache.get(key); + } else { + LocalSearchHints configuration = overrideDefaultHints(key.getQuery()); + IOperationCompiler compiler = getOperationCompiler(backendContext, configuration); + IPlanDescriptor plan = planProvider.getPlan(backendContext, compiler, + resultProviderRequestor, configuration, key); + planCache.put(key, plan); + return plan; + } + } + + private LocalSearchHints overrideDefaultHints(PQuery pQuery) { + if (hintCache.containsKey(pQuery)) { + return hintCache.get(pQuery); + } else { + LocalSearchHints hint = LocalSearchHints.getDefaultOverriddenBy( + computeOverridingHints(pQuery)); + hintCache.put(pQuery, hint); + return hint; + } + } + + /** + * Combine with {@link QueryHintOption#getValueOrDefault(QueryEvaluationHint)} to access + * hint settings not covered by {@link LocalSearchHints} + */ + private QueryEvaluationHint computeOverridingHints(PQuery pQuery) { + return backendContext.getHintProvider().getQueryEvaluationHint(pQuery).overrideBy(userHints); + } + + /** + * Prepare this result provider. This phase is separated from the constructor to allow the backend to cache its instance before + * requesting preparation for its dependencies. + * @since 1.5 + */ + public void prepare() { + try { + runtimeContext.coalesceTraversals(() -> { + LocalSearchHints configuration = overrideDefaultHints(query); + if (configuration.isUseBase()) { + indexInitializationBeforePlanning(); + } + prepareDirectDependencies(); + runtimeContext.executeAfterTraversal(AbstractLocalSearchResultProvider.this::preparePlansForExpectedAdornments); + return null; + }); + } catch (InvocationTargetException e) { + throw new QueryProcessingException("Error while building required indexes: {1}", new String[]{e.getTargetException().getMessage()}, "Error while building required indexes.", query, e); + } + } + + protected void preparePlansForExpectedAdornments() { + // Plan for possible adornments + for (Set adornment : overrideDefaultHints(query).getAdornmentProvider().getAdornments(query)) { + MatcherReference reference = new MatcherReference(query, adornment, userHints); + LocalSearchHints configuration = overrideDefaultHints(query); + IOperationCompiler compiler = getOperationCompiler(backendContext, configuration); + IPlanDescriptor plan = getOrCreatePlan(reference, backendContext, compiler, configuration, planProvider); + // Index keys + try { + if (configuration.isUseBase()) { + indexKeys(plan.getIteratedKeys()); + } + } catch (InvocationTargetException e) { + throw new QueryProcessingException(e.getMessage(), null, e.getMessage(), query, e); + } + //Prepare dependencies + for(SearchPlanForBody body: plan.getPlan()){ + for(CallWithAdornment dependency : body.getDependencies()){ + searchContext.getMatcher(dependency); + } + } + } + } + + protected void prepareDirectDependencies() { + // Do not prepare for any adornment at this point + IAdornmentProvider adornmentProvider = input -> Collections.emptySet(); + QueryEvaluationHint adornmentHint = IAdornmentProvider.toHint(adornmentProvider); + + for(IQueryReference call : getDirectDependencies()){ + resultProviderRequestor.requestResultProvider(call, adornmentHint); + } + } + + /** + * This method is called before planning start to allow indexing. It is important to note that this method is called + * inside a coalesceTraversals block, meaning (1) it is safe to add multiple registration requests as necessary, but + * (2) no value or statistics is available from the index. + * + * @throws ViatraQueryRuntimeException + */ + protected void indexInitializationBeforePlanning() { + // By default, no indexing is necessary + } + + /** + * Collects and indexes all types _directly_ referred by the PQuery {@link #query}. Types indirect + * @param requiredIndexingServices + */ + protected void indexReferredTypesOfQuery(PQuery query, IndexingService requiredIndexingServices) { + PQueries.directlyRequiredTypesOfQuery(query, true /*only enumerables are considered for indexing */).forEach( + inputKey -> runtimeContext.ensureIndexed(inputKey, requiredIndexingServices) + ); + } + + private Set getDirectDependencies() { + IFlattenCallPredicate flattenPredicate = overrideDefaultHints(query).getFlattenCallPredicate(); + Queue queue = new LinkedList<>(); + Set visited = new HashSet<>(); + Set result = new HashSet<>(); + queue.add(query); + + while(!queue.isEmpty()){ + PQuery next = queue.poll(); + visited.add(next); + for(PBody body : next.getDisjunctBodies().getBodies()){ + for (IQueryReference call : body.getConstraintsOfType(IQueryReference.class)) { + if (call instanceof PositivePatternCall && + flattenPredicate.shouldFlatten((PositivePatternCall) call)) + { + PQuery dep = ((PositivePatternCall) call).getReferredQuery(); + if (!visited.contains(dep)){ + queue.add(dep); + } + } else { + result.add(call); + } + } + } + } + return result; + } + + private LocalSearchMatcher initializeMatcher(Object[] parameters) { + return newLocalSearchMatcher(parameters); + } + + private LocalSearchMatcher initializeMatcher(TupleMask parameterSeedMask) { + return newLocalSearchMatcher(parameterSeedMask.transformUnique(query.getParameters())); + + } + + + /** + * @throws ViatraQueryRuntimeException + */ + public LocalSearchMatcher newLocalSearchMatcher(ITuple parameters) { + final Set adornment = new HashSet<>(); + for (int i = 0; i < parameters.getSize(); i++) { + if (parameters.get(i) != null) { + adornment.add(query.getParameters().get(i)); + } + } + + return newLocalSearchMatcher(adornment); + } + + /** + * @throws ViatraQueryRuntimeException + */ + public LocalSearchMatcher newLocalSearchMatcher(Object[] parameters) { + final Set adornment = new HashSet<>(); + for (int i = 0; i < parameters.length; i++) { + if (parameters[i] != null) { + adornment.add(query.getParameters().get(i)); + } + } + + return newLocalSearchMatcher(adornment); + } + + private LocalSearchMatcher newLocalSearchMatcher(final Set adornment) { + final MatcherReference reference = new MatcherReference(query, adornment, userHints); + + IPlanDescriptor plan = getOrCreatePlan(reference, planProvider); + if (overrideDefaultHints(reference.getQuery()).isUseBase()){ + try { + indexKeys(plan.getIteratedKeys()); + } catch (InvocationTargetException e) { + throw new LocalSearchException("Could not index keys", e); + } + } + + LocalSearchMatcher matcher = createMatcher(plan, searchContext); + matcher.addAdapters(backend.getAdapters()); + return matcher; + } + + private void indexKeys(final Iterable keys) throws InvocationTargetException { + final IQueryRuntimeContext qrc = getRuntimeContext(); + qrc.coalesceTraversals(new Callable() { + + @Override + public Void call() throws Exception { + for(IInputKey key : keys){ + if (key.isEnumerable()) { + qrc.ensureIndexed(key, IndexingService.INSTANCES); + } + } + return null; + } + }); + } + + @Override + public boolean hasMatch(Object[] parameters) { + final LocalSearchMatcher matcher = initializeMatcher(parameters); + return matcher.streamMatches(parameters).findAny().isPresent(); + } + + @Override + public boolean hasMatch(TupleMask parameterSeedMask, ITuple parameters) { + final LocalSearchMatcher matcher = initializeMatcher(parameterSeedMask); + return matcher.streamMatches(parameterSeedMask, parameters).findAny().isPresent(); + } + + @Override + public Optional getOneArbitraryMatch(Object[] parameters) { + final LocalSearchMatcher matcher = initializeMatcher(parameters); + return matcher.streamMatches(parameters).findAny(); + } + + @Override + public Optional getOneArbitraryMatch(TupleMask parameterSeedMask, ITuple parameters) { + final LocalSearchMatcher matcher = initializeMatcher(parameterSeedMask); + return matcher.streamMatches(parameterSeedMask, parameters).findAny(); + } + + @Override + public int countMatches(Object[] parameters) { + final LocalSearchMatcher matcher = initializeMatcher(parameters); + // Count returns long; casting to int - in case of integer overflow casting will throw the exception + return (int) matcher.streamMatches(parameters).count(); + } + + @Override + public int countMatches(TupleMask parameterSeedMask, ITuple parameters) { + final LocalSearchMatcher matcher = initializeMatcher(parameterSeedMask); + // Count returns long; casting to int - in case of integer overflow casting will throw the exception + return (int) matcher.streamMatches(parameterSeedMask, parameters).count(); + } + + private static final double ESTIMATE_CEILING = Long.MAX_VALUE / 16.0; + + @Override + public Optional estimateCardinality(TupleMask groupMask, Accuracy requiredAccuracy) { + if (Accuracy.BEST_UPPER_BOUND.atLeastAsPreciseAs(requiredAccuracy)) { // approximate using parameter types + final List parameters = query.getParameters(); + final Map, Set> dependencies = backendContext.getQueryAnalyzer() + .getProjectedFunctionalDependencies(query, false); + + List projectionIndices = groupMask.getIndicesAsList(); + + return estimateParameterCombinations(requiredAccuracy, parameters, dependencies, + projectionIndices, + Collections.emptySet() /* No parameters with fixed value */).map(Double::longValue); + } + else return Optional.empty(); + } + + @Override + public Optional estimateAverageBucketSize(TupleMask groupMask, Accuracy requiredAccuracy) { + if (Accuracy.BEST_UPPER_BOUND.atLeastAsPreciseAs(requiredAccuracy)) { // approximate using parameter types + final List parameters = query.getParameters(); + final Map, Set> dependencies = backendContext.getQueryAnalyzer() + .getProjectedFunctionalDependencies(query, false); + + // all parameters used for the estimation - determinized order + final List allParameterIndices = + IntStream.range(0, parameters.size()).boxed().collect(Collectors.toList()); + + // some free parameters are functionally determined by bound parameters + final Set boundOrImplied = FunctionalDependencyHelper.closureOf(groupMask.getIndicesAsList(), + dependencies); + + return estimateParameterCombinations(requiredAccuracy, parameters, dependencies, + allParameterIndices, + boundOrImplied); + } + else return Optional.empty(); + } + + /** + * @since 2.1 + * @noreference This method is not intended to be referenced by clients. + */ + public double estimateCost(TupleMask inputBindingMask) { + // TODO this is currently an abstract cost, not really a branching factor + + HashSet adornment = new HashSet<>(inputBindingMask.transform(query.getParameters())); + final MatcherReference reference = new MatcherReference(query, adornment, userHints); + IPlanDescriptor plan = getOrCreatePlan(reference, planProvider); + + return plan.getPlan().stream().mapToDouble(SearchPlanForBody::getCost).sum(); + } + + /** + * Approximates using parameter types + */ + private Optional estimateParameterCombinations( + Accuracy requiredAccuracy, + final List parameters, + final Map, Set> functionalDependencies, + final Collection parameterIndicesToEstimate, + final Set otherDeterminingIndices) + { + // keep order deterministic + LinkedHashSet freeParameterIndices = new LinkedHashSet<>(parameterIndicesToEstimate); + + // determining indices are bound + freeParameterIndices.removeAll(otherDeterminingIndices); + + // some free parameters are functionally determined by other free parameters + for (Integer candidateForRemoval : new ArrayList<>(freeParameterIndices)) { + List others = Stream.concat( + otherDeterminingIndices.stream(), + freeParameterIndices.stream().filter(index -> !Objects.equals(index, candidateForRemoval)) + ).collect(Collectors.toList()); + Set othersClosure = FunctionalDependencyHelper.closureOf(others, functionalDependencies); + if (othersClosure.contains(candidateForRemoval)) { + // other parameters functionally determine this mone, does not count towards estimate + freeParameterIndices.remove(candidateForRemoval); + } + } + + + Optional result = Optional.of(1.0); + // TODO this is currently works with declared types only. For better results, information from + // the Type inferrer should be included in the PSystem + for (int i = 0; (i < parameters.size()); i++) { + final IInputKey type = parameters.get(i).getDeclaredUnaryType(); + if (freeParameterIndices.contains(i) && type != null) { + result = result.flatMap(accumulator -> + runtimeContext.estimateCardinality(type, TupleMask.identity(1), requiredAccuracy).map(multiplier -> + Math.min(accumulator * multiplier, ESTIMATE_CEILING /* avoid overflow */) + )); + } + } + // TODO better approximate cardinality based on plan, branching factors, etc. + return result; + } + + + @Override + public Stream getAllMatches(Object[] parameters) { + final LocalSearchMatcher matcher = initializeMatcher(parameters); + return matcher.streamMatches(parameters); + } + + @Override + public Stream getAllMatches(TupleMask parameterSeedMask, ITuple parameters) { + final LocalSearchMatcher matcher = initializeMatcher(parameterSeedMask); + return matcher.streamMatches(parameterSeedMask, parameters); + } + + @Override + public IQueryBackend getQueryBackend() { + return backend; + } + + @Override + public void addUpdateListener(IUpdateable listener, Object listenerTag, boolean fireNow) { + // throw new UnsupportedOperationException(UPDATE_LISTENER_NOT_SUPPORTED); + } + + @Override + public void removeUpdateListener(Object listenerTag) { + // throw new UnsupportedOperationException(UPDATE_LISTENER_NOT_SUPPORTED); + } + + /** + * @since 1.4 + */ + public IMatcherCapability getCapabilites() { + LocalSearchHints configuration = overrideDefaultHints(query); + return configuration; + } + + /** + * Forgets all stored plans in this result provider. If no plans are stored, nothing happens. + * + * @since 2.0 + * @noreference This method is not intended to be referenced by clients; it should only used by {@link LocalSearchBackend}. + */ + public void forgetAllPlans() { + planCache.clear(); + } + + /** + * Returns a search plan for a given adornment if exists + * + * @return a search plan for the pattern with the given adornment, or null if none exists + * @since 2.0 + * @noreference This method is not intended to be referenced by clients; it should only used by {@link LocalSearchBackend}. + */ + public IPlanDescriptor getSearchPlan(Set adornment) { + return planCache.get(new MatcherReference(query, adornment)); + } +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/AllValidAdornments.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/AllValidAdornments.java new file mode 100644 index 00000000..f801163e --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/AllValidAdornments.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Grill Balázs, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.matcher.integration; + +import java.util.Set; +import java.util.stream.Collectors; + +import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameterDirection; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQueries; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.util.Sets; + + +/** + * This implementation calculates all valid adornments for the given query, respecting the parameter direction constraints. + * + * @author Grill Balázs + * @since 1.5 + */ +public class AllValidAdornments implements IAdornmentProvider { + + @Override + public Iterable> getAdornments(PQuery query) { + final Set ins = query.getParameters().stream().filter(PQueries.parameterDirectionPredicate(PParameterDirection.IN)).collect(Collectors.toSet()); + Set inouts = query.getParameters().stream().filter(PQueries.parameterDirectionPredicate(PParameterDirection.INOUT)).collect(Collectors.toSet()); + Set> possibleInouts = Sets.powerSet(inouts); + return possibleInouts.stream().map(input -> Sets.union(ins, input)).collect(Collectors.toSet()); + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/DontFlattenDisjunctive.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/DontFlattenDisjunctive.java new file mode 100644 index 00000000..bf1b61b5 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/DontFlattenDisjunctive.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.matcher.integration; + +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.PositivePatternCall; +import tools.refinery.viatra.runtime.matchers.psystem.rewriters.IFlattenCallPredicate; + +/** + * Forbids flattening of patterns that have more than one body. + * + * @since 2.1 + + * @author Gabor Bergmann + * + */ +public class DontFlattenDisjunctive implements IFlattenCallPredicate { + + @Override + public boolean shouldFlatten(PositivePatternCall positivePatternCall) { + return 1 >= positivePatternCall.getReferredQuery().getDisjunctBodies().getBodies().size(); + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/DontFlattenIncrementalPredicate.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/DontFlattenIncrementalPredicate.java new file mode 100644 index 00000000..1b918528 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/DontFlattenIncrementalPredicate.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Grill Balázs, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.matcher.integration; + +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint.BackendRequirement; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.PositivePatternCall; +import tools.refinery.viatra.runtime.matchers.psystem.rewriters.IFlattenCallPredicate; + +/** + * This implementation forbids flattening of patterns marked to be executed with a caching / incremental backend. + * This makes is possible for the user to configure hybrid matching via using + * the 'search' and 'incremental keywords in the pattern definition file. + * + * @since 1.5 + * + */ +public class DontFlattenIncrementalPredicate implements IFlattenCallPredicate { + + @Override + public boolean shouldFlatten(PositivePatternCall positivePatternCall) { + QueryEvaluationHint evaluationHints = positivePatternCall.getReferredQuery().getEvaluationHints(); + if (evaluationHints == null) return true; + + BackendRequirement backendRequirementType = evaluationHints.getQueryBackendRequirementType(); + switch(backendRequirementType) { + case DEFAULT_CACHING: + return false; + case SPECIFIC: + return !evaluationHints.getQueryBackendFactory().isCaching(); + case UNSPECIFIED: + case DEFAULT_SEARCH: + default: + return true; + } + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/GenericLocalSearchResultProvider.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/GenericLocalSearchResultProvider.java new file mode 100644 index 00000000..ed6c1e5f --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/GenericLocalSearchResultProvider.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Marton Bur, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.matcher.integration; + +import tools.refinery.viatra.runtime.localsearch.plan.IPlanProvider; +import tools.refinery.viatra.runtime.localsearch.planner.compiler.GenericOperationCompiler; +import tools.refinery.viatra.runtime.localsearch.planner.compiler.IOperationCompiler; +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; +import tools.refinery.viatra.runtime.matchers.context.IndexingService; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; + +/** + * @author Zoltan Ujhelyi + * @since 1.7 + * + */ +public class GenericLocalSearchResultProvider extends AbstractLocalSearchResultProvider { + + /** + * @throws ViatraQueryRuntimeException + */ + public GenericLocalSearchResultProvider(LocalSearchBackend backend, IQueryBackendContext context, PQuery query, + IPlanProvider planProvider, QueryEvaluationHint userHints) { + super(backend, context, query, planProvider, userHints); + } + + @Override + protected void indexInitializationBeforePlanning() { + super.indexInitializationBeforePlanning(); + + indexReferredTypesOfQuery(query, IndexingService.INSTANCES); + indexReferredTypesOfQuery(query, IndexingService.STATISTICS); + } + + @Override + protected IOperationCompiler getOperationCompiler(IQueryBackendContext backendContext, + LocalSearchHints configuration) { + return new GenericOperationCompiler(runtimeContext); + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/IAdornmentProvider.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/IAdornmentProvider.java new file mode 100644 index 00000000..86058be0 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/IAdornmentProvider.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Grill Balázs, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.matcher.integration; + +import java.util.Collections; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint.BackendRequirement; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; + +/** + * An adornment provider is used to define the adornments the pattern matcher should prepare for. + * + *

A default implementation is available in {@link AllValidAdornments} that describes all + * adornments fulfilling the parameter direction declarations; + * another default option (with better performance but restricted applicability) is {@link LazyPlanningAdornments}. + * + *

+ * + * Users may implement this interface to limit the number of prepared plans based on some runtime information: + * + *

+ * class SomeAdornments{
+ * 
+ *     public Iterable<Set<{@link PParameter}>> getAdornments({@link PQuery} query){
+ *         if (SomeGeneratedQuerySpecification.instance().getInternalQueryRepresentation().equals(query)){
+ *             return Collections.singleton(Sets.filter(Sets.newHashSet(query.getParameters()), new Predicate() {
+ *
+ *                  @Override
+ *                  public boolean apply(PParameter input) {
+ *                      // Decide whether this particular parameter will be bound
+ *                      return false;
+ *                  }
+ *              }));
+ *         }
+ *         // Returning an empty iterable is safe for unknown queries
+ *         return Collections.emptySet();
+ *     }
+ * 
+ * }
+ * 
+ * + * @author Grill Balázs + * @since 1.5 + * + */ +public interface IAdornmentProvider { + + /** + * The bound parameter sets + */ + public Iterable> getAdornments(PQuery query); + + /** + * @return a simple hint that only overrides the adornment provider + * @since 2.1 + */ + public static QueryEvaluationHint toHint(IAdornmentProvider adornmentProvider) { + return new QueryEvaluationHint( + Collections.singletonMap(LocalSearchHintOptions.ADORNMENT_PROVIDER, adornmentProvider), + BackendRequirement.UNSPECIFIED); + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LazyPlanningAdornments.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LazyPlanningAdornments.java new file mode 100644 index 00000000..30b3689f --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LazyPlanningAdornments.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.matcher.integration; + +import java.util.Collections; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; + +/** + * This adornment provider does not trigger the preparation of any plans. + * Actual query plans will be computed on demand, when the first actual match request is made with a given adornment. + * + *

Caution: this is a safe default adornment provider for {@link GenericLocalSearchResultProvider} only; + * do not use for the EMF-specific LS backend. + * + *

The benefits is in execution time: query planning costs for adornments are postponed until first usage + * or even entirely avoided (when adornment is never used in practice). + * However, query evaluation time may become less predictable, as the first matcher call (with a given adornment) + * will include the planning cost. + * For benchmarking or other purposes where this is not desirable, use an adornment provider that demands plan precomputation for all necessary adornments. + * + * @author Gabor Bergmann + * @since 2.1 + * + */ +public class LazyPlanningAdornments implements IAdornmentProvider { + + @Override + public Iterable> getAdornments(PQuery query) { + return Collections.emptySet(); + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchBackend.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchBackend.java new file mode 100644 index 00000000..ae51e2b0 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchBackend.java @@ -0,0 +1,259 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Marton Bur, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.matcher.integration; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; + +import tools.refinery.viatra.runtime.localsearch.exceptions.LocalSearchException; +import tools.refinery.viatra.runtime.localsearch.matcher.ILocalSearchAdapter; +import tools.refinery.viatra.runtime.localsearch.plan.IPlanDescriptor; +import tools.refinery.viatra.runtime.localsearch.plan.IPlanProvider; +import tools.refinery.viatra.runtime.localsearch.plan.SimplePlanProvider; +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; +import tools.refinery.viatra.runtime.matchers.backend.IMatcherCapability; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackend; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendHintProvider; +import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider; +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.backend.ResultProviderRequestor; +import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.psystem.analysis.QueryAnalyzer; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.ICache; +import tools.refinery.viatra.runtime.matchers.util.PurgableCache; + +/** + * @author Marton Bur, Zoltan Ujhelyi + * @noextend This class is not intended to be subclassed by clients. + */ +public abstract class LocalSearchBackend implements IQueryBackend { + + IQueryBackendContext context; + IPlanProvider planProvider; + private final Set adapters = new HashSet<>(); + + private final PurgableCache generalCache; + + private final Map> resultProviderCache = CollectionsFactory.createMap(); + + + /** + * @since 1.5 + */ + public LocalSearchBackend(IQueryBackendContext context) { + super(); + this.context = context; + this.generalCache = new PurgableCache(); + this.planProvider = new SimplePlanProvider(context.getLogger()); + } + + @Override + public void flushUpdates() { + + } + + @Override + public IQueryResultProvider getResultProvider(PQuery query) { + return getResultProvider(query, null); + } + + /** + * @since 1.4 + */ + @Override + public IQueryResultProvider getResultProvider(PQuery query, QueryEvaluationHint hints) { + + final QueryEvaluationHint callHints = getHintProvider().getQueryEvaluationHint(query).overrideBy(hints); + IMatcherCapability requestedCapability = context.getRequiredMatcherCapability(query, callHints); + for(AbstractLocalSearchResultProvider existingResultProvider : resultProviderCache.getOrDefault(query, Collections.emptyList())){ + if (requestedCapability.canBeSubstitute(existingResultProvider.getCapabilites())){ + return existingResultProvider; + } + } + + AbstractLocalSearchResultProvider resultProvider = initializeResultProvider(query, hints); + resultProviderCache.computeIfAbsent(query, k->new ArrayList<>()).add(resultProvider); + resultProvider.prepare(); + return resultProvider; + } + + /** + * Returns a requestor that this backend uses while processing pattern calls from this query. + * @noreference This method is not intended to be referenced by clients. + * @since 2.1 + */ + public ResultProviderRequestor getResultProviderRequestor(PQuery query, QueryEvaluationHint userHints) { + QueryEvaluationHint hintOnQuery = + context.getHintProvider().getQueryEvaluationHint(query).overrideBy(userHints); + LocalSearchHints defaultsApplied = LocalSearchHints.getDefaultOverriddenBy(hintOnQuery); + + return new ResultProviderRequestor(this, + context.getResultProviderAccess(), + context.getHintProvider(), + defaultsApplied.getCallDelegationStrategy(), + userHints, + /* no global overrides */ null); + } + + /** + * @throws ViatraQueryRuntimeException + * @since 1.7 + */ + protected abstract AbstractLocalSearchResultProvider initializeResultProvider(PQuery query, QueryEvaluationHint hints); + + @Override + public void dispose() { + resultProviderCache.clear(); + generalCache.purge(); + } + + @Override + public boolean isCaching() { + return false; + } + + /** + * @since 2.0 + */ + @Override + public AbstractLocalSearchResultProvider peekExistingResultProvider(PQuery query) { + return resultProviderCache.getOrDefault(query, Collections.emptyList()).stream().findAny().orElse(null); + } + + /** + * @since 1.4 + */ + public IQueryRuntimeContext getRuntimeContext() { + return context.getRuntimeContext(); + } + + + /** + * @since 1.5 + */ + public QueryAnalyzer getQueryAnalyzer() { + return context.getQueryAnalyzer(); + } + + + /** + * @since 1.4 + */ + public IQueryBackendHintProvider getHintProvider() { + return context.getHintProvider(); + } + + /** + * @since 1.5 + */ + public void addAdapter(ILocalSearchAdapter adapter){ + adapters.add(adapter); + } + + /** + * @since 1.5 + */ + public void removeAdapter(ILocalSearchAdapter adapter){ + adapters.remove(adapter); + } + + /** + * Return a copy of the current adapters + * @since 1.7 + */ + public List getAdapters() { + return new ArrayList<>(adapters); + } + + /** + * @since 1.5 + */ + public IQueryBackendContext getBackendContext() { + return context; + } + + /** + * Returns the internal cache of the backend + * @since 1.7 + * @noreference This method is not intended to be referenced by clients. + */ + public ICache getCache() { + return generalCache; + } + + /** + * Updates the previously stored search plans for one or more given queries, computing a new set of plans if + * necessary. The new plans created are the same that would be created by executing prepare on the given query + * definitions. + * + * @since 2.0 + */ + public void recomputePlans(PQuery... queries) { + recomputePlans(Arrays.stream(queries).flatMap(query -> resultProviderCache.getOrDefault(query, Collections.emptyList()).stream())); + } + + /** + * Updates the previously stored search plans for one or more given queries, computing a new set of plans if + * necessary The new plans created are the same that would be created by executing prepare on the given query + * definitions. + * + * @since 2.0 + */ + public void recomputePlans(Collection queries) { + recomputePlans(queries.stream().flatMap(query -> resultProviderCache.getOrDefault(query, Collections.emptyList()).stream())); + } + + /** + * Updates the previously stored search plans for one or more given queries, computing a new set of plans if + * necessary The new plans created are the same that would be created by executing prepare on the given query + * definitions. + * + * @since 2.0 + */ + public void recomputePlans() { + recomputePlans(resultProviderCache.values().stream().flatMap(List::stream)); + } + + private void recomputePlans(Stream resultProviders) { + try { + context.getRuntimeContext().coalesceTraversals(() -> { + resultProviders.forEach(resultProvider -> { + resultProvider.forgetAllPlans(); + resultProvider.prepare(); + }); + return null; + }); + } catch (InvocationTargetException e) { + throw new LocalSearchException("Error while rebuilding plans: " + e.getMessage(), e); + } + } + + /** + * Returns a search plan for a given query and adornment if such plan is already calculated. + * + * @return a previously calculated search plan for the given query and adornment, or null if no such plan exists + * @since 2.0 + */ + public IPlanDescriptor getSearchPlan(PQuery query, Set adornment) { + final AbstractLocalSearchResultProvider resultProvider = peekExistingResultProvider(query); + return (resultProvider == null) ? null : resultProvider.getSearchPlan(adornment); + } +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchBackendFactoryProvider.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchBackendFactoryProvider.java new file mode 100644 index 00000000..bba381d3 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchBackendFactoryProvider.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.matcher.integration; + +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactory; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactoryProvider; + +/** + * @since 2.0 + */ +public class LocalSearchBackendFactoryProvider implements IQueryBackendFactoryProvider { + + @Override + public IQueryBackendFactory getFactory() { + return LocalSearchEMFBackendFactory.INSTANCE; + } + + @Override + public boolean isSystemDefaultSearchBackend() { + return true; + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchEMFBackendFactory.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchEMFBackendFactory.java new file mode 100644 index 00000000..5bffebac --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchEMFBackendFactory.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Marton Bur, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.matcher.integration; + +import tools.refinery.viatra.runtime.matchers.backend.IMatcherCapability; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackend; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactory; +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; + +/** + * @author Marton Bur, Zoltan Ujhelyi + * @since 2.0 + * + */ +public enum LocalSearchEMFBackendFactory implements IQueryBackendFactory { + + + INSTANCE; + + /** + * @since 1.5 + */ + @Override + public IQueryBackend create(IQueryBackendContext context) { + return new LocalSearchBackend(context) { + + @Override + protected AbstractLocalSearchResultProvider initializeResultProvider(PQuery query, QueryEvaluationHint hints) { + return new LocalSearchResultProvider(this, context, query, planProvider, hints); + } + + @Override + public IQueryBackendFactory getFactory() { + return INSTANCE; + } + }; + } + + @Override + public Class getBackendClass() { + return LocalSearchBackend.class; + } + + /** + * @since 1.4 + */ + @Override + public IMatcherCapability calculateRequiredCapability(PQuery query, QueryEvaluationHint hint) { + return LocalSearchHints.parse(hint); + } + + @Override + public boolean isCaching() { + return false; + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchGenericBackendFactory.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchGenericBackendFactory.java new file mode 100644 index 00000000..1dd08f98 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchGenericBackendFactory.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Marton Bur, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.matcher.integration; + +import tools.refinery.viatra.runtime.matchers.backend.IMatcherCapability; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackend; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactory; +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; + +/** + * @author Marton Bur, Zoltan Ujhelyi + * @since 1.7 + * + */ +public enum LocalSearchGenericBackendFactory implements IQueryBackendFactory { + + INSTANCE; + + /** + * @since 1.5 + */ + @Override + public IQueryBackend create(IQueryBackendContext context) { + return new LocalSearchBackend(context) { + + @Override + protected AbstractLocalSearchResultProvider initializeResultProvider(PQuery query, QueryEvaluationHint hints) { + return new GenericLocalSearchResultProvider(this, context, query, planProvider, hints); + } + + @Override + public IQueryBackendFactory getFactory() { + return INSTANCE; + } + + }; + } + + @Override + public Class getBackendClass() { + return LocalSearchBackend.class; + } + + /** + * @since 1.4 + */ + @Override + public IMatcherCapability calculateRequiredCapability(PQuery query, QueryEvaluationHint hint) { + return LocalSearchHints.parse(hint); + } + + @Override + public boolean isCaching() { + return false; + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchGenericBackendFactoryProvider.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchGenericBackendFactoryProvider.java new file mode 100644 index 00000000..ea422d91 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchGenericBackendFactoryProvider.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.matcher.integration; + +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactory; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactoryProvider; + +/** + * @since 2.0 + */ +public class LocalSearchGenericBackendFactoryProvider implements IQueryBackendFactoryProvider { + + @Override + public IQueryBackendFactory getFactory() { + return LocalSearchGenericBackendFactory.INSTANCE; + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchHintOptions.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchHintOptions.java new file mode 100644 index 00000000..43462204 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchHintOptions.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.matcher.integration; + +import tools.refinery.viatra.runtime.localsearch.planner.cost.ICostFunction; +import tools.refinery.viatra.runtime.localsearch.planner.cost.impl.IndexerBasedConstraintCostFunction; +import tools.refinery.viatra.runtime.matchers.backend.ICallDelegationStrategy; +import tools.refinery.viatra.runtime.matchers.backend.QueryHintOption; +import tools.refinery.viatra.runtime.matchers.psystem.rewriters.IFlattenCallPredicate; + +/** + * + * @author Gabor Bergmann + * @since 1.5 + * @noinstantiate This class is not intended to be instantiated by clients. + */ +public final class LocalSearchHintOptions { + + private LocalSearchHintOptions() { + // Private constructor for utility class + } + + public static final QueryHintOption USE_BASE_INDEX = + hintOption("USE_BASE_INDEX", true); + + // This key can be used to influence the core planner algorithm + public static final QueryHintOption PLANNER_TABLE_ROW_COUNT = + hintOption("PLANNER_TABLE_ROW_COUNT", 4); + /** + * Cost function to be used by the planner. Must implement {@link ICostFunction} + * @since 1.4 + */ + public static final QueryHintOption PLANNER_COST_FUNCTION = + hintOption("PLANNER_COST_FUNCTION", new IndexerBasedConstraintCostFunction()); + /** + * Predicate to decide whether to flatten specific positive pattern calls {@link IFlattenCallPredicate} + * @since 1.4 + */ + public static final QueryHintOption FLATTEN_CALL_PREDICATE = + hintOption("FLATTEN_CALL_PREDICATE", new DontFlattenDisjunctive()); + /** + * Strategy to decide how hints (most importantly, backend selection) propagate across pattern calls. + * Must implement {@link ICallDelegationStrategy}. + * @since 2.1 + */ + public static final QueryHintOption CALL_DELEGATION_STRATEGY = + hintOption("CALL_DELEGATION_STRATEGY", ICallDelegationStrategy.FULL_BACKEND_ADHESION); + + /** + * A provider of expected adornments {@link IAdornmentProvider}. + * + * The safe default is {@link AllValidAdornments}; + * however, the generic backend variant may safely use {@link LazyPlanningAdornments} instead. + * + * @since 1.5 + */ + public static final QueryHintOption ADORNMENT_PROVIDER = + hintOption("ADORNMENT_PROVIDER", new AllValidAdornments()); + + // internal helper for conciseness + private static QueryHintOption hintOption(String hintKeyLocalName, V defaultValue) { + return new QueryHintOption<>(LocalSearchHintOptions.class, hintKeyLocalName, defaultValue); + } +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchHints.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchHints.java new file mode 100644 index 00000000..75f338b6 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchHints.java @@ -0,0 +1,357 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Grill Balázs, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.matcher.integration; + +import static tools.refinery.viatra.runtime.localsearch.matcher.integration.LocalSearchHintOptions.ADORNMENT_PROVIDER; +import static tools.refinery.viatra.runtime.localsearch.matcher.integration.LocalSearchHintOptions.CALL_DELEGATION_STRATEGY; +import static tools.refinery.viatra.runtime.localsearch.matcher.integration.LocalSearchHintOptions.FLATTEN_CALL_PREDICATE; +import static tools.refinery.viatra.runtime.localsearch.matcher.integration.LocalSearchHintOptions.PLANNER_COST_FUNCTION; +import static tools.refinery.viatra.runtime.localsearch.matcher.integration.LocalSearchHintOptions.PLANNER_TABLE_ROW_COUNT; +import static tools.refinery.viatra.runtime.localsearch.matcher.integration.LocalSearchHintOptions.USE_BASE_INDEX; +import static tools.refinery.viatra.runtime.matchers.backend.CommonQueryHintOptions.normalizationTraceCollector; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import tools.refinery.viatra.runtime.localsearch.planner.cost.ICostFunction; +import tools.refinery.viatra.runtime.localsearch.planner.cost.impl.IndexerBasedConstraintCostFunction; +import tools.refinery.viatra.runtime.localsearch.planner.cost.impl.StatisticsBasedConstraintCostFunction; +import tools.refinery.viatra.runtime.localsearch.planner.cost.impl.VariableBindingBasedCostFunction; +import tools.refinery.viatra.runtime.matchers.backend.ICallDelegationStrategy; +import tools.refinery.viatra.runtime.matchers.backend.IMatcherCapability; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactory; +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.backend.QueryHintOption; +import tools.refinery.viatra.runtime.matchers.psystem.rewriters.DefaultFlattenCallPredicate; +import tools.refinery.viatra.runtime.matchers.psystem.rewriters.IFlattenCallPredicate; +import tools.refinery.viatra.runtime.matchers.psystem.rewriters.IRewriterTraceCollector; +import tools.refinery.viatra.runtime.matchers.psystem.rewriters.NeverFlattenCallPredicate; +import tools.refinery.viatra.runtime.matchers.psystem.rewriters.NopTraceCollector; + +/** + * Type safe builder and extractor for Local search specific hints + * + * @author Grill Balázs + * @since 1.4 + * + */ +public final class LocalSearchHints implements IMatcherCapability { + + private Boolean useBase = null; + + private Integer rowCount = null; + + private ICostFunction costFunction = null; + + private IFlattenCallPredicate flattenCallPredicate = null; + + private ICallDelegationStrategy callDelegationStrategy = null; + + private IAdornmentProvider adornmentProvider = null; + + private IRewriterTraceCollector traceCollector = NopTraceCollector.INSTANCE; + + private IQueryBackendFactory backendFactory = null; + + private LocalSearchHints() {} + + /** + * Return the default settings overridden by the given hints + */ + public static LocalSearchHints getDefaultOverriddenBy(QueryEvaluationHint overridingHint){ + return parse(getDefault().build(overridingHint)); + } + + /** + * Default settings which are considered the most safe, providing a reasonable performance for most of the cases. Assumes the availability of the base indexer. + */ + public static LocalSearchHints getDefault(){ + LocalSearchHints result = new LocalSearchHints(); + result.useBase = USE_BASE_INDEX.getDefaultValue(); + result.rowCount = PLANNER_TABLE_ROW_COUNT.getDefaultValue(); + result.costFunction = PLANNER_COST_FUNCTION.getDefaultValue(); + result.flattenCallPredicate = FLATTEN_CALL_PREDICATE.getDefaultValue(); + result.callDelegationStrategy = CALL_DELEGATION_STRATEGY.getDefaultValue(); + result.adornmentProvider = ADORNMENT_PROVIDER.getDefaultValue(); + result.backendFactory = LocalSearchEMFBackendFactory.INSTANCE; + return result; + } + + /** + * With this setting, the patterns are flattened before planning. This may cause performance gain in some cases compared to the {@link #getDefault()} settings, + * However this should be used with care for patterns containing calls with several bodies. + */ + public static LocalSearchHints getDefaultFlatten(){ + LocalSearchHints result = new LocalSearchHints(); + result.useBase = true; + result.rowCount = 4; + result.costFunction = new IndexerBasedConstraintCostFunction(); + result.flattenCallPredicate = new DefaultFlattenCallPredicate(); + result.callDelegationStrategy = CALL_DELEGATION_STRATEGY.getDefaultValue(); + result.adornmentProvider = ADORNMENT_PROVIDER.getDefaultValue(); + result.backendFactory = LocalSearchEMFBackendFactory.INSTANCE; + return result; + } + + /** + * Settings to be used when the base index is not available. + */ + public static LocalSearchHints getDefaultNoBase(){ + LocalSearchHints result = new LocalSearchHints(); + result.useBase = false; + result.rowCount = 4; + result.costFunction = new VariableBindingBasedCostFunction(); + result.flattenCallPredicate = new NeverFlattenCallPredicate(); + result.callDelegationStrategy = ICallDelegationStrategy.FULL_BACKEND_ADHESION; + result.adornmentProvider = ADORNMENT_PROVIDER.getDefaultValue(); + result.backendFactory = LocalSearchEMFBackendFactory.INSTANCE; + return result; + } + + /** + * Initializes the generic (not EMF specific) search backend with the default settings + * @since 1.7 + */ + public static LocalSearchHints getDefaultGeneric(){ + LocalSearchHints result = new LocalSearchHints(); + result.useBase = true; // Should be unused; but a false value might cause surprises as an engine-default hint + result.rowCount = 4; + result.costFunction = new IndexerBasedConstraintCostFunction(StatisticsBasedConstraintCostFunction.INVERSE_NAVIGATION_PENALTY_GENERIC); + result.flattenCallPredicate = FLATTEN_CALL_PREDICATE.getDefaultValue(); + result.callDelegationStrategy = ICallDelegationStrategy.FULL_BACKEND_ADHESION; + result.adornmentProvider = new LazyPlanningAdornments(); + result.backendFactory = LocalSearchGenericBackendFactory.INSTANCE; + return result; + } + + /** + * Initializes the default search backend with hybrid-enabled settings + * @since 2.1 + */ + public static LocalSearchHints getDefaultHybrid(){ + LocalSearchHints result = getDefault(); + result.callDelegationStrategy = ICallDelegationStrategy.PARTIAL_BACKEND_ADHESION; + result.flattenCallPredicate = new IFlattenCallPredicate.And( + new DontFlattenIncrementalPredicate(), new DontFlattenDisjunctive()); + return result; + } + + /** + * Initializes the generic (not EMF specific) search backend with hybrid-enabled settings + * @since 2.1 + */ + public static LocalSearchHints getDefaultGenericHybrid(){ + LocalSearchHints result = getDefaultGeneric(); + result.callDelegationStrategy = ICallDelegationStrategy.PARTIAL_BACKEND_ADHESION; + result.flattenCallPredicate = new IFlattenCallPredicate.And( + new DontFlattenIncrementalPredicate(), new DontFlattenDisjunctive()); + return result; + } + + public static LocalSearchHints parse(QueryEvaluationHint hint){ + LocalSearchHints result = new LocalSearchHints(); + + result.useBase = USE_BASE_INDEX.getValueOrNull(hint); + result.rowCount = PLANNER_TABLE_ROW_COUNT.getValueOrNull(hint); + result.flattenCallPredicate = FLATTEN_CALL_PREDICATE.getValueOrNull(hint); + result.callDelegationStrategy = CALL_DELEGATION_STRATEGY.getValueOrNull(hint); + result.costFunction = PLANNER_COST_FUNCTION.getValueOrNull(hint); + result.adornmentProvider = ADORNMENT_PROVIDER.getValueOrNull(hint); + result.traceCollector = normalizationTraceCollector.getValueOrDefault(hint); + + return result; + } + + + private Map, Object> calculateHintMap() { + Map, Object> map = new HashMap<>(); + if (useBase != null){ + USE_BASE_INDEX.insertOverridingValue(map, useBase); + } + if (rowCount != null){ + PLANNER_TABLE_ROW_COUNT.insertOverridingValue(map, rowCount); + } + if (costFunction != null){ + PLANNER_COST_FUNCTION.insertOverridingValue(map, costFunction); + } + if (flattenCallPredicate != null){ + FLATTEN_CALL_PREDICATE.insertOverridingValue(map, flattenCallPredicate); + } + if (callDelegationStrategy != null){ + CALL_DELEGATION_STRATEGY.insertOverridingValue(map, callDelegationStrategy); + } + if (adornmentProvider != null){ + ADORNMENT_PROVIDER.insertOverridingValue(map, adornmentProvider); + } + if (traceCollector != null){ + normalizationTraceCollector.insertOverridingValue(map, traceCollector); + } + return map; + } + + public QueryEvaluationHint build(){ + Map, Object> map = calculateHintMap(); + return new QueryEvaluationHint(map, backendFactory); + } + + /** + * @since 1.7 + */ + public QueryEvaluationHint build(QueryEvaluationHint overridingHint) { + if (overridingHint == null) + return build(); + + IQueryBackendFactory factory = (overridingHint.getQueryBackendFactory() == null) + ? this.backendFactory + : overridingHint.getQueryBackendFactory(); + + Map, Object> hints = calculateHintMap(); + if (overridingHint.getBackendHintSettings() != null) { + hints.putAll(overridingHint.getBackendHintSettings()); + } + + return new QueryEvaluationHint(hints, factory); + } + + public boolean isUseBase() { + return useBase; + } + + public ICostFunction getCostFunction() { + return costFunction; + } + + public IFlattenCallPredicate getFlattenCallPredicate() { + return flattenCallPredicate; + } + + /** + * @since 2.1 + */ + public ICallDelegationStrategy getCallDelegationStrategy() { + return callDelegationStrategy; + } + + public Integer getRowCount() { + return rowCount; + } + + /** + * @since 1.5 + */ + public IAdornmentProvider getAdornmentProvider() { + return adornmentProvider; + } + + /** + * @since 1.6 + */ + public IRewriterTraceCollector getTraceCollector() { + return traceCollector == null ? normalizationTraceCollector.getDefaultValue() : traceCollector; + } + + public LocalSearchHints setUseBase(boolean useBase) { + this.useBase = useBase; + return this; + } + + public LocalSearchHints setRowCount(int rowCount) { + this.rowCount = rowCount; + return this; + } + + public LocalSearchHints setCostFunction(ICostFunction costFunction) { + this.costFunction = costFunction; + return this; + } + + public LocalSearchHints setFlattenCallPredicate(IFlattenCallPredicate flattenCallPredicate) { + this.flattenCallPredicate = flattenCallPredicate; + return this; + } + + + /** + * @since 2.1 + */ + public LocalSearchHints setCallDelegationStrategy(ICallDelegationStrategy callDelegationStrategy) { + this.callDelegationStrategy = callDelegationStrategy; + return this; + } + + /** + * @since 1.6 + */ + public LocalSearchHints setTraceCollector(IRewriterTraceCollector traceCollector) { + this.traceCollector = traceCollector; + return this; + } + + /** + * @since 1.5 + */ + public LocalSearchHints setAdornmentProvider(IAdornmentProvider adornmentProvider) { + this.adornmentProvider = adornmentProvider; + return this; + } + + public static LocalSearchHints customizeUseBase(boolean useBase){ + return new LocalSearchHints().setUseBase(useBase); + } + + public static LocalSearchHints customizeRowCount(int rowCount){ + return new LocalSearchHints().setRowCount(rowCount); + } + + public static LocalSearchHints customizeCostFunction(ICostFunction costFunction){ + return new LocalSearchHints().setCostFunction(costFunction); + } + + public static LocalSearchHints customizeFlattenCallPredicate(IFlattenCallPredicate predicate){ + return new LocalSearchHints().setFlattenCallPredicate(predicate); + } + + /** + * @since 2.1 + */ + public static LocalSearchHints customizeCallDelegationStrategy(ICallDelegationStrategy strategy){ + return new LocalSearchHints().setCallDelegationStrategy(strategy); + } + + /** + * @since 1.5 + */ + public static LocalSearchHints customizeAdornmentProvider(IAdornmentProvider adornmentProvider){ + return new LocalSearchHints().setAdornmentProvider(adornmentProvider); + } + + /** + * @since 1.6 + */ + public static LocalSearchHints customizeTraceCollector(IRewriterTraceCollector traceCollector){ + return new LocalSearchHints().setTraceCollector(traceCollector); + } + + @Override + public boolean canBeSubstitute(IMatcherCapability capability) { + if (capability instanceof LocalSearchHints){ + LocalSearchHints other = (LocalSearchHints)capability; + /* + * We allow substitution of matchers if their functionally relevant settings are equal. + */ + return Objects.equals(other.useBase, useBase); + } + /* + * For any other cases (e.g. for Rete), we cannot assume + * that matchers created by LS are functionally equivalent. + */ + return false; + } +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchResultProvider.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchResultProvider.java new file mode 100644 index 00000000..a3017b18 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/LocalSearchResultProvider.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Marton Bur, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.matcher.integration; + +import tools.refinery.viatra.runtime.localsearch.plan.IPlanProvider; +import tools.refinery.viatra.runtime.localsearch.planner.compiler.EMFOperationCompiler; +import tools.refinery.viatra.runtime.localsearch.planner.compiler.IOperationCompiler; +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; +import tools.refinery.viatra.runtime.matchers.context.IndexingService; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; + +/** + * @author Marton Bur, Zoltan Ujhelyi + * + */ +public class LocalSearchResultProvider extends AbstractLocalSearchResultProvider { + + /** + * @throws ViatraQueryRuntimeException + * @since 1.5 + */ + public LocalSearchResultProvider(LocalSearchBackend backend, IQueryBackendContext context, PQuery query, + IPlanProvider planProvider) { + this(backend, context, query, planProvider, null); + } + + /** + * @throws ViatraQueryRuntimeException + * @since 1.5 + */ + public LocalSearchResultProvider(LocalSearchBackend backend, IQueryBackendContext context, PQuery query, + IPlanProvider planProvider, QueryEvaluationHint userHints) { + super(backend, context, query, planProvider, userHints); + } + + @Override + protected void indexInitializationBeforePlanning() { + super.indexInitializationBeforePlanning(); + + indexReferredTypesOfQuery(query, IndexingService.STATISTICS); + } + + @Override + protected IOperationCompiler getOperationCompiler(IQueryBackendContext backendContext, + LocalSearchHints configuration) { + return new EMFOperationCompiler(runtimeContext, configuration.isUseBase()); + } +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/CheckOperationExecutor.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/CheckOperationExecutor.java new file mode 100644 index 00000000..295ac110 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/CheckOperationExecutor.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations; + +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation.ISearchOperationExecutor; + +/** + * Abstract base class for search operations that check only the already set variables. + * + * @noextend This class is not intended to be subclassed by clients. + * @since 2.0 + */ +public abstract class CheckOperationExecutor implements ISearchOperationExecutor { + + /** + * The executed field ensures that the second call of the check always returns false, resulting in a quick + * backtracking. + */ + private boolean executed; + + @Override + public void onInitialize(MatchingFrame frame, ISearchContext context) { + executed = false; + } + + @Override + public void onBacktrack(MatchingFrame frame, ISearchContext context) { + } + + @Override + public boolean execute(MatchingFrame frame, ISearchContext context) { + executed = executed ? false : check(frame, context); + return executed; + } + + /** + * Executes the checking operation + * @since 1.7 + */ + protected abstract boolean check(MatchingFrame frame, ISearchContext context) ; + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/ExtendOperationExecutor.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/ExtendOperationExecutor.java new file mode 100644 index 00000000..01179118 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/ExtendOperationExecutor.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Zoltan Ujhelyi, Akos Horvath, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations; + +import java.util.Iterator; + +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation.ISearchOperationExecutor; + +/** + * An operation that can be used to enumerate all possible values for a single position based on a constraint + * @author Zoltan Ujhelyi, Akos Horvath + * @noextend This class is not intended to be subclassed by clients. + * @since 2.0 + */ +public abstract class ExtendOperationExecutor implements ISearchOperationExecutor { + + private Iterator it; + + /** + * Returns an iterator with the possible options from the current state + * @since 2.0 + */ + protected abstract Iterator getIterator(MatchingFrame frame, ISearchContext context); + /** + * Updates the frame with the next element of the iterator. Called during {@link #execute(MatchingFrame, ISearchContext)}. + * + * @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. + * @since 2.0 + */ + protected abstract boolean fillInValue(T newValue, MatchingFrame frame, ISearchContext context); + + /** + * Restores the frame to the state before {@link #fillInValue(Object, MatchingFrame, ISearchContext)}. Called during + * {@link #onBacktrack(MatchingFrame, ISearchContext)}. + * + * @since 2.0 + */ + protected abstract void cleanup(MatchingFrame frame, ISearchContext context); + + @Override + public void onInitialize(MatchingFrame frame, ISearchContext context) { + it = getIterator(frame, context); + } + + @Override + public void onBacktrack(MatchingFrame frame, ISearchContext context) { + it = null; + + } + + @Override + public boolean execute(MatchingFrame frame, ISearchContext context) { + if (it.hasNext()){ + T newValue = it.next(); + while(!fillInValue(newValue, frame, context) && it.hasNext()){ + newValue = it.next(); + } + return true; + } else { + return false; + } + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/IIteratingSearchOperation.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/IIteratingSearchOperation.java new file mode 100644 index 00000000..6fee0097 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/IIteratingSearchOperation.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Grill Balázs, IncQueryLabs + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations; + +import tools.refinery.viatra.runtime.matchers.context.IInputKey; + +/** + * Denotes a {@link ISearchOperation} which involves iterating over an instances of an {@link IInputKey} + * + * @author Grill Balázs + * @since 1.4 + * + */ +public interface IIteratingSearchOperation extends ISearchOperation{ + + /** + * Get the {@link IInputKey} which instances this operation iterates upon. + */ + public IInputKey getIteratedInputKey(); + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/IPatternMatcherOperation.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/IPatternMatcherOperation.java new file mode 100644 index 00000000..d8106329 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/IPatternMatcherOperation.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations; + +import tools.refinery.viatra.runtime.localsearch.operations.util.CallInformation; + +/** + * Marker interface for pattern matcher call operations, such as positive and negative pattern calls or match aggregators. + * + * @author Zoltan Ujhelyi + * @since 1.7 + */ +public interface IPatternMatcherOperation { + + /** + * Returns the precomputed call information associated with the current operation + * @since 2.0 + */ + CallInformation getCallInformation(); +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/ISearchOperation.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/ISearchOperation.java new file mode 100644 index 00000000..eb6243ed --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/ISearchOperation.java @@ -0,0 +1,88 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Zoltan Ujhelyi, Akos Horvath, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations; + +import java.util.List; +import java.util.function.Function; + +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; + +/** + * Represents a search operation executable by the LS engine. It is expected that an operation can be shared among + * multiple LS matchers, but the created executors are not. + * + * @author Zoltan Ujhelyi + * + */ +public interface ISearchOperation { + + /** + * Initializes a new operation executor for the given operation. Repeated calls must return different executor + * instances. + * + * @since 2.0 + */ + public ISearchOperationExecutor createExecutor(); + + /** + * + * @since 2.0 + * + */ + public interface ISearchOperationExecutor { + + /** + * Returns the stateless operation this executor was initialized from + */ + ISearchOperation getOperation(); + + /** + * During the execution of the corresponding plan, the onInitialize callback is evaluated before the execution of + * the operation may begin. Operations may use this method to initialize its internal data structures. + * + * @throws ViatraQueryRuntimeException + */ + void onInitialize(MatchingFrame frame, ISearchContext context); + + /** + * After the execution of the operation failed and {@link #execute(MatchingFrame, ISearchContext)} returns false, the onBacktrack + * callback is evaluated. Operations may use this method to clean up any temporary structures, and make the + * operation ready for a new execution. + * + * @throws ViatraQueryRuntimeException + */ + void onBacktrack(MatchingFrame frame, ISearchContext context); + + /** + * + * @param frame + * @param context + * @return true if successful, or false if backtracking needed + * @throws ViatraQueryRuntimeException + */ + boolean execute(MatchingFrame frame, ISearchContext context); + } + /** + * + * @return the ordered list of the variable numbers that are affected by the search operation + */ + List getVariablePositions(); + + /** + * Creates a string representation of the search operation by replacing the variable numbers according to the + * parameter function. It is expected that the provided function does return a non-null value for each variable + * index that is returned by {@link #getVariablePositions()}; otherwise a {@link NullPointerException} will be + * thrown during the calculation of the string. + * + * @since 2.0 + */ + String toString(Function variableMapping); +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/MatchingFrameValueProvider.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/MatchingFrameValueProvider.java new file mode 100644 index 00000000..38f62129 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/MatchingFrameValueProvider.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations; + +import java.util.Map; + +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.matchers.psystem.IValueProvider; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; + +/** + * + * + * @author Zoltan Ujhelyi + * + */ +public class MatchingFrameValueProvider implements IValueProvider { + + final Map nameMap; + final MatchingFrame frame; + + public MatchingFrameValueProvider(MatchingFrame frame, Map nameMap) { + super(); + this.frame = frame; + this.nameMap = nameMap; + } + + @Override + public Object getValue(String variableName) { + Integer index = nameMap.get(variableName); + Preconditions.checkArgument(index != null, "Unknown parameter variable name"); + return frame.get(index); + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/AggregatorCheck.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/AggregatorCheck.java new file mode 100644 index 00000000..4d631635 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/AggregatorCheck.java @@ -0,0 +1,116 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Grill Balázs, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations.check; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Stream; + +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.CheckOperationExecutor; +import tools.refinery.viatra.runtime.localsearch.operations.IPatternMatcherOperation; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; +import tools.refinery.viatra.runtime.localsearch.operations.util.CallInformation; +import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider; +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.AggregatorConstraint; +import tools.refinery.viatra.runtime.matchers.tuple.VolatileModifiableMaskedTuple; + +/** + * Calculates the aggregated value of a column based on the given {@link AggregatorConstraint} + * + * @author Balázs Grill + * @since 1.4 + * @noextend This class is not intended to be subclassed by clients. + */ +public class AggregatorCheck implements ISearchOperation, IPatternMatcherOperation { + + private class Executor extends CheckOperationExecutor { + + private final VolatileModifiableMaskedTuple maskedTuple; + private IQueryResultProvider matcher; + + public Executor() { + super(); + this.maskedTuple = new VolatileModifiableMaskedTuple(information.getThinFrameMask()); + } + + @Override + public void onInitialize(MatchingFrame frame, ISearchContext context) { + super.onInitialize(frame, context); + maskedTuple.updateTuple(frame); + matcher = context.getMatcher(information.getCallWithAdornment()); + } + + @Override + protected boolean check(MatchingFrame frame, ISearchContext context) { + IMultisetAggregationOperator operator = aggregator.getAggregator().getOperator(); + Object result = aggregate(operator, aggregator.getAggregatedColumn(), frame); + return result == null ? false : Objects.equals(frame.getValue(position), result); + } + + @SuppressWarnings("unchecked") + private AggregateResult aggregate( + IMultisetAggregationOperator operator, int aggregatedColumn, + MatchingFrame initialFrame) { + maskedTuple.updateTuple(initialFrame); + final Stream valueStream = matcher.getAllMatches(information.getParameterMask(), maskedTuple) + .map(match -> (Domain) match.get(aggregatedColumn)); + return operator.aggregateStream(valueStream); + } + + @Override + public ISearchOperation getOperation() { + return AggregatorCheck.this; + } + } + + private final int position; + private final AggregatorConstraint aggregator; + private final CallInformation information; + + /** + * @since 1.7 + */ + public AggregatorCheck(CallInformation information, AggregatorConstraint aggregator, int position) { + super(); + this.information = information; + this.position = position; + this.aggregator = aggregator; + } + + @Override + public ISearchOperationExecutor createExecutor() { + return new Executor(); + } + + @Override + public List getVariablePositions() { + return Collections.singletonList(position); + } + + @Override + public String toString() { + return toString(Object::toString); + } + + @Override + public String toString(Function variableMapping) { + return "check "+variableMapping.apply(position)+" = " + aggregator.getAggregator().getOperator().getName() + " find " + information.toString(variableMapping); + } + + @Override + public CallInformation getCallInformation() { + return information; + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/BinaryTransitiveClosureCheck.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/BinaryTransitiveClosureCheck.java new file mode 100644 index 00000000..8f818542 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/BinaryTransitiveClosureCheck.java @@ -0,0 +1,135 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations.check; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.Queue; +import java.util.Set; +import java.util.function.Function; + +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.CheckOperationExecutor; +import tools.refinery.viatra.runtime.localsearch.operations.IPatternMatcherOperation; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; +import tools.refinery.viatra.runtime.localsearch.operations.util.CallInformation; +import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; + +/** + * Checking for a transitive closure expressed as a local search pattern matcher. The matched pattern must have two + * parameters of the same model type. + * + * @author Zoltan Ujhelyi + * @noextend This class is not intended to be subclassed by clients. + * @noinstantiate This class is not intended to be instantiated by clients. + * + */ +public class BinaryTransitiveClosureCheck implements ISearchOperation, IPatternMatcherOperation { + + private class Executor extends CheckOperationExecutor { + + @Override + public void onInitialize(MatchingFrame frame, ISearchContext context) { + super.onInitialize(frame, context); + matcher = context.getMatcher(information.getCallWithAdornment()); + // Note: second parameter is NOT bound during execution, but the first is + } + + @Override + protected boolean check(MatchingFrame frame, ISearchContext context) { + if (checkReflexive(frame)) { + return true; + } + + Object targetValue = frame.get(targetPosition); + Queue sourcesToEvaluate = new LinkedList<>(); + sourcesToEvaluate.add(frame.get(sourcePosition)); + Set sourceEvaluated = new HashSet<>(); + final Object[] mappedFrame = new Object[] {null, null}; + while (!sourcesToEvaluate.isEmpty()) { + Object currentValue = sourcesToEvaluate.poll(); + sourceEvaluated.add(currentValue); + mappedFrame[0] = currentValue; + for (Tuple match : (Iterable) () -> matcher.getAllMatches(mappedFrame).iterator()) { + Object foundTarget = match.get(1); + if (targetValue.equals(foundTarget)) { + return true; + } else if (!sourceEvaluated.contains(foundTarget)) { + sourcesToEvaluate.add(foundTarget); + } + } + } + return false; + } + + protected boolean checkReflexive(MatchingFrame frame) { + return reflexive && Objects.equals(frame.get(sourcePosition), frame.get(targetPosition)); + } + + @Override + public ISearchOperation getOperation() { + return BinaryTransitiveClosureCheck.this; + } + } + + private final CallInformation information; + private IQueryResultProvider matcher; + private final int sourcePosition; + private final int targetPosition; + private final boolean reflexive; + + /** + * The source position will be matched in the called pattern to the first parameter; while target to the second. + *

+ * NOTE: the reflexive check call does not include the parameter type checks; appropriate type checks should be + * added as necessary by the operation compiler. + * + * @since 2.0 + */ + public BinaryTransitiveClosureCheck(CallInformation information, int sourcePosition, int targetPosition, boolean reflexive) { + super(); + this.sourcePosition = sourcePosition; + this.targetPosition = targetPosition; + this.information = information; + this.reflexive = reflexive; + } + + @Override + public ISearchOperationExecutor createExecutor() { + return new Executor(); + } + + @Override + public String toString() { + return toString(Object::toString); + } + + @Override + public String toString(Function variableMapping) { + String c = information.toString(variableMapping); + int p = c.indexOf('('); + String modifier = reflexive ? "*" : "+"; + return "check find "+c.substring(0, p)+ modifier +c.substring(p); + } + + @Override + public List getVariablePositions() { + return Arrays.asList(sourcePosition, targetPosition); + } + + @Override + public CallInformation getCallInformation() { + return information; + } +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/CheckConstant.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/CheckConstant.java new file mode 100644 index 00000000..4ce48af0 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/CheckConstant.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Marton Bur, Akos Horvath, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations.check; + +import java.util.Collections; +import java.util.List; +import java.util.function.Function; + +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.CheckOperationExecutor; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; + +/** + * This operation handles constants in search plans by checking if a variable is bound to a certain constant value. Such + * operations should be executed as early as possible during plan execution. + * + * @author Marton Bur + * @noextend This class is not intended to be subclassed by clients. + */ +public class CheckConstant implements ISearchOperation { + + private class Executor extends CheckOperationExecutor { + + @Override + protected boolean check(MatchingFrame frame, ISearchContext context) { + return frame.get(position).equals(value); + } + + @Override + public ISearchOperation getOperation() { + return CheckConstant.this; + } + } + + private int position; + private Object value; + + public CheckConstant(int position, Object value) { + this.position = position; + this.value = value; + } + + @Override + public ISearchOperationExecutor createExecutor() { + return new Executor(); + } + + @Override + public List getVariablePositions() { + return Collections.singletonList(position); + } + + @Override + public String toString() { + return toString(Object::toString); + } + + @Override + public String toString(Function variableMapping) { + return "check constant "+variableMapping.apply(position)+"='"+value+"'"; + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/CheckPositivePatternCall.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/CheckPositivePatternCall.java new file mode 100644 index 00000000..5f9b688a --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/CheckPositivePatternCall.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Grill Balázs, IncQueryLabs + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations.check; + +import java.util.List; +import java.util.function.Function; + +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.CheckOperationExecutor; +import tools.refinery.viatra.runtime.localsearch.operations.IPatternMatcherOperation; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; +import tools.refinery.viatra.runtime.localsearch.operations.util.CallInformation; +import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider; +import tools.refinery.viatra.runtime.matchers.tuple.VolatileModifiableMaskedTuple; + +/** + * @author Grill Balázs + * @since 1.4 + * @noextend This class is not intended to be subclassed by clients. + */ +public class CheckPositivePatternCall implements ISearchOperation, IPatternMatcherOperation { + + private class Executor extends CheckOperationExecutor { + + private final VolatileModifiableMaskedTuple maskedTuple; + private IQueryResultProvider matcher; + + public Executor() { + super(); + this.maskedTuple = new VolatileModifiableMaskedTuple(information.getThinFrameMask()); + } + + @Override + public void onInitialize(MatchingFrame frame, ISearchContext context) { + super.onInitialize(frame, context); + maskedTuple.updateTuple(frame); + matcher = context.getMatcher(information.getCallWithAdornment()); + } + + /** + * @since 1.5 + */ + protected boolean check(MatchingFrame frame, ISearchContext context) { + return matcher.hasMatch(information.getParameterMask(), maskedTuple); + } + + @Override + public ISearchOperation getOperation() { + return CheckPositivePatternCall.this; + } + } + + private final CallInformation information; + + + /** + * @since 1.7 + */ + public CheckPositivePatternCall(CallInformation information) { + super(); + this.information = information; + + } + + @Override + public ISearchOperationExecutor createExecutor() { + return new Executor(); + } + + @Override + public List getVariablePositions() { + return information.getVariablePositions(); + } + + @Override + public String toString() { + return toString(Object::toString); + } + + @Override + public String toString(Function variableMapping) { + return "check find "+information.toString(variableMapping); + } + + @Override + public CallInformation getCallInformation() { + return information; + } +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/ContainmentCheck.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/ContainmentCheck.java new file mode 100644 index 00000000..9dfb16f5 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/ContainmentCheck.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations.check; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.emf.ecore.util.EcoreUtil; +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.exceptions.LocalSearchException; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.CheckOperationExecutor; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; + +/** + * A simple operation that checks whether a {@link EStructuralFeature} connects two selected variables. + * @noextend This class is not intended to be subclassed by clients. + */ +public class ContainmentCheck implements ISearchOperation { + + private class Executor extends CheckOperationExecutor { + + @Override + protected boolean check(MatchingFrame frame, ISearchContext context) { + try { + EObject child = (EObject) frame.getValue(childPosition); + EObject container = (EObject)frame.getValue(containerPosition); + + if (transitive) { + return EcoreUtil.isAncestor(container, child); + } else { + return child.eContainer().equals(container); + } + } catch (ClassCastException e) { + throw new LocalSearchException(LocalSearchException.TYPE_ERROR, e); + } + } + + @Override + public ISearchOperation getOperation() { + return ContainmentCheck.this; + } + } + + int childPosition; + int containerPosition; + private boolean transitive; + + public ContainmentCheck(int childPosition, int containerPosition, boolean transitive) { + super(); + this.childPosition = childPosition; + this.containerPosition = containerPosition; + this.transitive = transitive; + } + + @Override + public ISearchOperationExecutor createExecutor() { + return new Executor(); + } + + @Override + public String toString() { + return toString(Object::toString); + } + + @Override + public String toString(Function variableMapping) { + return "check containment +"+variableMapping.apply(containerPosition)+" <>--> +"+childPosition+(transitive ? " transitively" : " directly"); + } + + @Override + public List getVariablePositions() { + return Arrays.asList(childPosition, containerPosition); + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/CountCheck.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/CountCheck.java new file mode 100644 index 00000000..d74c1c6f --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/CountCheck.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations.check; + +import java.util.Collections; +import java.util.List; +import java.util.function.Function; + +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.CheckOperationExecutor; +import tools.refinery.viatra.runtime.localsearch.operations.IPatternMatcherOperation; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; +import tools.refinery.viatra.runtime.localsearch.operations.util.CallInformation; +import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider; +import tools.refinery.viatra.runtime.matchers.tuple.VolatileModifiableMaskedTuple; + +/** + * Calculates the count of matches for a called matcher + * + * @author Zoltan Ujhelyi + * @noextend This class is not intended to be subclassed by clients. + */ +public class CountCheck implements ISearchOperation, IPatternMatcherOperation { + + private class Executor extends CheckOperationExecutor { + + private final VolatileModifiableMaskedTuple maskedTuple; + private IQueryResultProvider matcher; + + public Executor() { + maskedTuple = new VolatileModifiableMaskedTuple(information.getThinFrameMask()); + } + + @Override + public void onInitialize(MatchingFrame frame, ISearchContext context) { + super.onInitialize(frame, context); + maskedTuple.updateTuple(frame); + matcher = context.getMatcher(information.getCallWithAdornment()); + } + + @Override + protected boolean check(MatchingFrame frame, ISearchContext context) { + int count = matcher.countMatches(information.getParameterMask(), maskedTuple); + return ((Integer)frame.getValue(position)) == count; + } + + @Override + public ISearchOperation getOperation() { + return CountCheck.this; + } + } + + private final int position; + private final CallInformation information; + + /** + * @since 1.7 + */ + public CountCheck(CallInformation information, int position) { + super(); + this.information = information; + this.position = position; + } + + + @Override + public ISearchOperationExecutor createExecutor() { + return new Executor(); + } + + @Override + public List getVariablePositions() { + return Collections.singletonList(position); + } + + @Override + public String toString(Function variableMapping) { + return "check "+variableMapping.apply(position)+" = count find "+ information.toString(variableMapping); + } + + @Override + public CallInformation getCallInformation() { + return information; + } +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/ExpressionCheck.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/ExpressionCheck.java new file mode 100644 index 00000000..fc9efdca --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/ExpressionCheck.java @@ -0,0 +1,78 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations.check; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.CheckOperationExecutor; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; +import tools.refinery.viatra.runtime.localsearch.operations.MatchingFrameValueProvider; +import tools.refinery.viatra.runtime.matchers.psystem.IExpressionEvaluator; + +/** + * @author Zoltan Ujhelyi + * @noextend This class is not intended to be subclassed by clients. + */ +public class ExpressionCheck implements ISearchOperation { + + private class Executor extends CheckOperationExecutor { + + @Override + protected boolean check(MatchingFrame frame, ISearchContext context) { + try { + boolean result = (Boolean) evaluator.evaluateExpression(new MatchingFrameValueProvider(frame, nameMap)); + return result; + } catch (Exception e) { + context.getLogger().warn("Error while evaluating expression", e); + return false; + } + } + + @Override + public ISearchOperation getOperation() { + return ExpressionCheck.this; + } + } + + IExpressionEvaluator evaluator; + Map nameMap; + + public ExpressionCheck(IExpressionEvaluator evaluator, Map nameMap) { + super(); + this.evaluator = evaluator; + this.nameMap = nameMap; + } + + @Override + public ISearchOperationExecutor createExecutor() { + return new Executor(); + } + + @Override + public List getVariablePositions() { + // XXX not sure if this is the correct implementation to get the affected variable indicies + return new ArrayList<>(nameMap.values()); + } + + @Override + public String toString() { + return toString(Object::toString); + } + + @Override + public String toString(Function variableMapping) { + return "check expression "+evaluator.getShortDescription(); + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/ExpressionEvalCheck.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/ExpressionEvalCheck.java new file mode 100644 index 00000000..5b1bca10 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/ExpressionEvalCheck.java @@ -0,0 +1,95 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Grill Balázs, IncQueryLabs + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations.check; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.CheckOperationExecutor; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; +import tools.refinery.viatra.runtime.localsearch.operations.MatchingFrameValueProvider; +import tools.refinery.viatra.runtime.matchers.psystem.IExpressionEvaluator; + +/** + * @author Grill Balázs + * @since 1.3 + * @noextend This class is not intended to be subclassed by clients. + */ +public class ExpressionEvalCheck implements ISearchOperation { + + private class Executor extends CheckOperationExecutor { + + @Override + protected boolean check(MatchingFrame frame, ISearchContext context) { + try { + Object result = evaluator.evaluateExpression(new MatchingFrameValueProvider(frame, nameMap)); + if (!unwind && result != null) { + Object currentValue = frame.get(outputPosition); + return result.equals(currentValue); + } else if (unwind && result instanceof Set) { + Object currentValue = frame.get(outputPosition); + return ((Set)result).contains(currentValue); + } + } catch (Exception e) { + context.getLogger().warn("Error while evaluating expression", e); + } + return false; + } + + @Override + public ISearchOperation getOperation() { + return ExpressionEvalCheck.this; + } + } + + private final int outputPosition; + private final IExpressionEvaluator evaluator; + private final Map nameMap; + private final boolean unwind; + + public ExpressionEvalCheck(IExpressionEvaluator evaluator, Map nameMap, int position) { + this(evaluator, nameMap, false, position); + } + + /** + * @since 2.7 + */ + public ExpressionEvalCheck(IExpressionEvaluator evaluator, Map nameMap, boolean unwind, int position) { + this.evaluator = evaluator; + this.nameMap = nameMap; + this.unwind = unwind; + this.outputPosition = position; + } + + @Override + public ISearchOperationExecutor createExecutor() { + return new Executor(); + } + + @Override + public List getVariablePositions() { + // XXX not sure if this is the correct implementation to get the affected variable indicies + return new ArrayList<>(nameMap.values()); + } + + @Override + public String toString() { + return toString(Object::toString); + } + + @Override + public String toString(Function variableMapping) { + return "check "+variableMapping.apply(outputPosition)+" = expression "+evaluator.getShortDescription(); + } +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/InequalityCheck.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/InequalityCheck.java new file mode 100644 index 00000000..3f30c3c4 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/InequalityCheck.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations.check; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; + +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.exceptions.LocalSearchException; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.CheckOperationExecutor; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; + +/** + * @author Zoltan Ujhelyi + * @noextend This class is not intended to be subclassed by clients. + */ +public class InequalityCheck implements ISearchOperation { + + private class Executor extends CheckOperationExecutor { + + @Override + protected boolean check(MatchingFrame frame, ISearchContext context) { + Object source = frame.getValue(sourceLocation); + Object target = frame.getValue(targetLocation); + if (source == null) { + throw new LocalSearchException("Source not bound."); + } + if (target == null) { + throw new LocalSearchException("Target not bound"); + } + return !source.equals(target); + } + + @Override + public ISearchOperation getOperation() { + return InequalityCheck.this; + } + } + + int sourceLocation; + int targetLocation; + + public InequalityCheck(int sourceLocation, int targetLocation) { + super(); + this.sourceLocation = sourceLocation; + this.targetLocation = targetLocation; + } + + @Override + public ISearchOperationExecutor createExecutor() { + return new Executor(); + } + + @Override + public String toString() { + return toString(Object::toString); + } + + @Override + public String toString(Function variableMapping) { + return "check "+variableMapping.apply(sourceLocation)+" != "+variableMapping.apply(targetLocation); + } + + @Override + public List getVariablePositions() { + return Arrays.asList(sourceLocation, targetLocation); + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/InstanceOfClassCheck.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/InstanceOfClassCheck.java new file mode 100644 index 00000000..68d92040 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/InstanceOfClassCheck.java @@ -0,0 +1,75 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations.check; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EObject; +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.CheckOperationExecutor; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; + +/** + * @author Zoltan Ujhelyi + * @noextend This class is not intended to be subclassed by clients. + */ +public class InstanceOfClassCheck implements ISearchOperation { + + private class Executor extends CheckOperationExecutor { + + @Override + protected boolean check(MatchingFrame frame, ISearchContext context) { + Objects.requireNonNull(frame.getValue(position), () -> String.format("Invalid plan, variable %s unbound", position)); + if (frame.getValue(position) instanceof EObject) { + return clazz.isSuperTypeOf(((EObject) frame.getValue(position)).eClass()); + } + return false; + } + + @Override + public ISearchOperation getOperation() { + return InstanceOfClassCheck.this; + } + } + + private int position; + private EClass clazz; + + public InstanceOfClassCheck(int position, EClass clazz) { + this.position = position; + this.clazz = clazz; + + } + + @Override + public ISearchOperationExecutor createExecutor() { + return new Executor(); + } + + @Override + public String toString() { + return toString(Object::toString); + } + + @Override + public String toString(Function variableMapping) { + return "check "+clazz.getName()+"(+"+ variableMapping.apply(position)+")"; + } + + @Override + public List getVariablePositions() { + return Arrays.asList(position); + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/InstanceOfDataTypeCheck.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/InstanceOfDataTypeCheck.java new file mode 100644 index 00000000..940104a2 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/InstanceOfDataTypeCheck.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations.check; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; + +import org.eclipse.emf.ecore.EDataType; +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.CheckOperationExecutor; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; + +/** + * @author Zoltan Ujhelyi + * @noextend This class is not intended to be subclassed by clients. + */ +public class InstanceOfDataTypeCheck implements ISearchOperation { + + private class Executor extends CheckOperationExecutor { + + @Override + protected boolean check(MatchingFrame frame, ISearchContext context) { + Objects.requireNonNull(frame.getValue(position), () -> String.format("Invalid plan, variable %s unbound", position)); + return dataType.isInstance(frame.getValue(position)); + } + + @Override + public ISearchOperation getOperation() { + return InstanceOfDataTypeCheck.this; + } + } + + private int position; + private EDataType dataType; + + public InstanceOfDataTypeCheck(int position, EDataType dataType) { + this.position = position; + this.dataType = dataType; + + } + + @Override + public ISearchOperationExecutor createExecutor() { + return new Executor(); + } + + @Override + public String toString() { + return toString(Object::toString); + } + + @Override + public String toString(Function variableMapping) { + return "check "+dataType.getName()+"(+"+variableMapping.apply(position)+")"; + } + + @Override + public List getVariablePositions() { + return Arrays.asList(position); + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/InstanceOfJavaClassCheck.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/InstanceOfJavaClassCheck.java new file mode 100644 index 00000000..1da312a0 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/InstanceOfJavaClassCheck.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations.check; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; + +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.CheckOperationExecutor; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; + +/** + * @author Zoltan Ujhelyi + * @since 1.4 + * @noextend This class is not intended to be subclassed by clients. + */ +public class InstanceOfJavaClassCheck implements ISearchOperation { + + private class Executor extends CheckOperationExecutor { + + @Override + protected boolean check(MatchingFrame frame, ISearchContext context) { + Objects.requireNonNull(frame.getValue(position), () -> String.format("Invalid plan, variable %s unbound", position)); + return clazz.isInstance(frame.getValue(position)); + } + + @Override + public ISearchOperation getOperation() { + return InstanceOfJavaClassCheck.this; + } + } + + private int position; + private Class clazz; + + public InstanceOfJavaClassCheck(int position, Class clazz) { + this.position = position; + this.clazz = clazz; + + } + + @Override + public ISearchOperationExecutor createExecutor() { + return new Executor(); + } + + @Override + public String toString() { + return toString(Object::toString); + } + + @Override + public String toString(Function variableMapping) { + return "check java "+clazz.getName()+"(+"+variableMapping.apply(position)+")"; + } + + @Override + public List getVariablePositions() { + return Arrays.asList(position); + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/NACOperation.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/NACOperation.java new file mode 100644 index 00000000..3759e1d1 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/NACOperation.java @@ -0,0 +1,89 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations.check; + +import java.util.List; +import java.util.function.Function; + +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.CheckOperationExecutor; +import tools.refinery.viatra.runtime.localsearch.operations.IPatternMatcherOperation; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; +import tools.refinery.viatra.runtime.localsearch.operations.util.CallInformation; +import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider; +import tools.refinery.viatra.runtime.matchers.tuple.VolatileModifiableMaskedTuple; + +/** + * @author Zoltan Ujhelyi + * @noextend This class is not intended to be subclassed by clients. + */ +public class NACOperation implements ISearchOperation, IPatternMatcherOperation { + + private class Executor extends CheckOperationExecutor { + private final VolatileModifiableMaskedTuple maskedTuple; + private IQueryResultProvider matcher; + + private Executor() { + this.maskedTuple = new VolatileModifiableMaskedTuple(information.getThinFrameMask()); + } + + @Override + public void onInitialize(MatchingFrame frame, ISearchContext context) { + super.onInitialize(frame, context); + maskedTuple.updateTuple(frame); + matcher = context.getMatcher(information.getCallWithAdornment()); + } + + @Override + protected boolean check(MatchingFrame frame, ISearchContext context) { + return !matcher.hasMatch(information.getParameterMask(), maskedTuple); + } + + @Override + public ISearchOperation getOperation() { + return NACOperation.this; + } + } + + private final CallInformation information; + + /** + * @since 1.7 + */ + public NACOperation(CallInformation information) { + super(); + this.information = information; + } + + @Override + public ISearchOperationExecutor createExecutor() { + return new Executor(); + } + + @Override + public String toString() { + return toString(Object::toString); + } + + @Override + public String toString(Function variableMapping) { + return "check neg find "+information.toString(variableMapping); + } + + @Override + public List getVariablePositions() { + return information.getVariablePositions(); + } + + @Override + public CallInformation getCallInformation() { + return information; + } +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/StructuralFeatureCheck.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/StructuralFeatureCheck.java new file mode 100644 index 00000000..a3e5bc40 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/StructuralFeatureCheck.java @@ -0,0 +1,91 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations.check; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EStructuralFeature; +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.exceptions.LocalSearchException; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.CheckOperationExecutor; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; + +/** + * A simple operation that checks whether a {@link EStructuralFeature} connects two selected variables. + * @noextend This class is not intended to be subclassed by clients. + */ +public class StructuralFeatureCheck implements ISearchOperation { + + private class Executor extends CheckOperationExecutor { + + @Override + protected boolean check(MatchingFrame frame, ISearchContext context) { + Objects.requireNonNull(frame.getValue(sourcePosition), () -> String.format("Invalid plan, variable %s unbound", sourcePosition)); + Objects.requireNonNull(frame.getValue(targetPosition), () -> String.format("Invalid plan, variable %s unbound", targetPosition)); + try { + EObject source = (EObject) frame.getValue(sourcePosition); + if(! feature.getEContainingClass().isSuperTypeOf(source.eClass()) ){ + // TODO planner should ensure the proper supertype relation, see bug 500968 + return false; + } + Object target = frame.getValue(targetPosition); + if (feature.isMany()) { + return ((Collection) source.eGet(feature)).contains(target); + } else { + return target.equals(source.eGet(feature)); + } + } catch (ClassCastException e) { + throw new LocalSearchException(LocalSearchException.TYPE_ERROR, e); + } + } + + @Override + public ISearchOperation getOperation() { + return StructuralFeatureCheck.this; + } + } + + int sourcePosition; + int targetPosition; + EStructuralFeature feature; + + public StructuralFeatureCheck(int sourcePosition, int targetPosition, EStructuralFeature feature) { + super(); + this.sourcePosition = sourcePosition; + this.targetPosition = targetPosition; + this.feature = feature; + } + + @Override + public ISearchOperationExecutor createExecutor() { + return new Executor(); + } + + @Override + public String toString() { + return toString(Object::toString); + } + + @Override + public String toString(Function variableMapping) { + return "check "+feature.getContainerClass().getSimpleName()+"."+feature.getName()+"(+"+variableMapping.apply(sourcePosition)+", +"+variableMapping.apply(targetPosition)+")"; + } + + @Override + public List getVariablePositions() { + return Arrays.asList(sourcePosition, targetPosition); + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/nobase/ScopeCheck.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/nobase/ScopeCheck.java new file mode 100644 index 00000000..06989fdc --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/check/nobase/ScopeCheck.java @@ -0,0 +1,91 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Marton Bur, Zoltan Ujhelyi, Akos Horvath, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations.check.nobase; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.util.EcoreUtil; +import tools.refinery.viatra.runtime.base.api.filters.IBaseIndexObjectFilter; +import tools.refinery.viatra.runtime.emf.EMFScope; +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.CheckOperationExecutor; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; + +/** + * This operation simply checks if a model element is part of the Query Scope + * + * @author Marton Bur + * + */ +public class ScopeCheck implements ISearchOperation { + private class Executor extends CheckOperationExecutor { + + @Override + protected boolean check(MatchingFrame frame, ISearchContext context) { + Objects.requireNonNull(frame.getValue(position), () -> String.format("Invalid plan, variable %d unbound", position)); + Object value = frame.getValue(position); + if(value instanceof EObject){ + EObject eObject = (EObject) value; + IBaseIndexObjectFilter filterConfiguration = scope.getOptions().getObjectFilterConfiguration(); + boolean filtered = false; + if(filterConfiguration != null){ + filtered = filterConfiguration.isFiltered(eObject); + } + if(filtered){ + return false; + } else { + return EcoreUtil.isAncestor(scope.getScopeRoots(), eObject); + } + } else { + return true; + } + } + + @Override + public ISearchOperation getOperation() { + return ScopeCheck.this; + } + + } + + private int position; + private EMFScope scope; + + public ScopeCheck(int position, EMFScope scope) { + this.position = position; + this.scope = scope; + + } + + @Override + public ISearchOperationExecutor createExecutor() { + return new Executor(); + } + + @Override + public String toString() { + return toString(Object::toString); + } + + + @Override + public String toString(Function variableMapping) { + return "check +"+variableMapping.apply(position) +" in scope "+scope; + } + + @Override + public List getVariablePositions() { + return Arrays.asList(position); + } +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/AggregatorExtend.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/AggregatorExtend.java new file mode 100644 index 00000000..c2e75b7a --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/AggregatorExtend.java @@ -0,0 +1,106 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Grill Balázs, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations.extend; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Stream; + +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.IPatternMatcherOperation; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; +import tools.refinery.viatra.runtime.localsearch.operations.util.CallInformation; +import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider; +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.AggregatorConstraint; +import tools.refinery.viatra.runtime.matchers.tuple.VolatileModifiableMaskedTuple; + +/** + * Calculates the aggregated value of a column based on the given {@link AggregatorConstraint} + * + * @author Balázs Grill + * @since 1.4 + */ +public class AggregatorExtend implements ISearchOperation, IPatternMatcherOperation{ + + private class Executor extends SingleValueExtendOperationExecutor { + + private final VolatileModifiableMaskedTuple maskedTuple; + private IQueryResultProvider matcher; + + public Executor(int position) { + super(position); + this.maskedTuple = new VolatileModifiableMaskedTuple(information.getThinFrameMask()); + } + + @Override + public Iterator getIterator(MatchingFrame frame, ISearchContext context) { + maskedTuple.updateTuple(frame); + matcher = context.getMatcher(information.getCallWithAdornment()); + Object aggregate = aggregate(aggregator.getAggregator().getOperator(), aggregator.getAggregatedColumn()); + return aggregate == null ? Collections.emptyIterator() : Collections.singletonList(aggregate).iterator(); + + } + + @SuppressWarnings("unchecked") + private AggregateResult aggregate( + IMultisetAggregationOperator operator, int aggregatedColumn) { + final Stream valueStream = matcher.getAllMatches(information.getParameterMask(), maskedTuple) + .map(match -> (Domain) match.get(aggregatedColumn)); + return operator.aggregateStream(valueStream); + } + + @Override + public ISearchOperation getOperation() { + return AggregatorExtend.this; + } + } + + private final AggregatorConstraint aggregator; + private final CallInformation information; + private final int position; + + /** + * @since 1.7 + */ + public AggregatorExtend(CallInformation information, AggregatorConstraint aggregator, int position) { + this.aggregator = aggregator; + this.information = information; + this.position = position; + } + + @Override + public ISearchOperationExecutor createExecutor() { + return new Executor(position); + } + + @Override + public List getVariablePositions() { + return Arrays.asList(position); + } + + @Override + public String toString() { + return toString(Object::toString); + } + + @Override + public String toString(Function variableMapping) { + return "extend -"+variableMapping.apply(position)+" = " + aggregator.getAggregator().getOperator().getName()+" find " + information.toString(variableMapping); + } + + @Override + public CallInformation getCallInformation() { + return information; + } +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/CountOperation.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/CountOperation.java new file mode 100644 index 00000000..08ecc8d6 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/CountOperation.java @@ -0,0 +1,88 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations.extend; + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.function.Function; + +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.IPatternMatcherOperation; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; +import tools.refinery.viatra.runtime.localsearch.operations.util.CallInformation; +import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider; +import tools.refinery.viatra.runtime.matchers.tuple.VolatileModifiableMaskedTuple; + +/** + * Calculates the count of matches for a called matcher + * + * @author Zoltan Ujhelyi + */ +public class CountOperation implements ISearchOperation, IPatternMatcherOperation{ + + private class Executor extends SingleValueExtendOperationExecutor { + private final VolatileModifiableMaskedTuple maskedTuple; + private IQueryResultProvider matcher; + + public Executor(int position) { + super(position); + maskedTuple = new VolatileModifiableMaskedTuple(information.getThinFrameMask()); + } + + @Override + public Iterator getIterator(MatchingFrame frame, ISearchContext context) { + matcher = context.getMatcher(information.getCallWithAdornment()); + maskedTuple.updateTuple(frame); + return Collections.singletonList(matcher.countMatches(information.getParameterMask(), maskedTuple)).iterator(); + } + + @Override + public ISearchOperation getOperation() { + return CountOperation.this; + } + } + + private final CallInformation information; + private final int position; + + /** + * @since 1.7 + */ + public CountOperation(CallInformation information, int position) { + this.information = information; + this.position = position; + } + + @Override + public ISearchOperationExecutor createExecutor() { + return new Executor(position); + } + + @Override + public List getVariablePositions() { + return information.getVariablePositions(); + } + + @Override + public String toString() { + return toString(Object::toString); + } + + @Override + public String toString(Function variableMapping) { + return "extend -"+variableMapping.apply(position)+" = count find " + information.toString(variableMapping); + } + + @Override + public CallInformation getCallInformation() { + return information; + } +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExpressionEval.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExpressionEval.java new file mode 100644 index 00000000..7186f4ac --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExpressionEval.java @@ -0,0 +1,104 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations.extend; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; +import tools.refinery.viatra.runtime.localsearch.operations.MatchingFrameValueProvider; +import tools.refinery.viatra.runtime.matchers.psystem.IExpressionEvaluator; + +/** + * Calculates the result of an expression and stores it inside a variable for future reference. + * + * @author Zoltan Ujhelyi + * + */ +public class ExpressionEval implements ISearchOperation { + + private class Executor extends SingleValueExtendOperationExecutor { + + public Executor(int position) { + super(position); + } + + @Override + public Iterator getIterator(MatchingFrame frame, ISearchContext context) { + try { + Object result = evaluator.evaluateExpression(new MatchingFrameValueProvider(frame, nameMap)); + if (!unwind && result != null){ + return Collections.singletonList(result).iterator(); + } else if (unwind && result instanceof Set) { + return ((Set)result).iterator(); + } else { + return Collections.emptyIterator(); + } + } catch (Exception e) { + context.getLogger().warn("Error while evaluating expression", e); + return Collections.emptyIterator(); + } + } + + @Override + public ISearchOperation getOperation() { + return ExpressionEval.this; + } + } + + private final IExpressionEvaluator evaluator; + private final boolean unwind; + private final Map nameMap; + private final int position; + + public ExpressionEval(IExpressionEvaluator evaluator, Map nameMap, int position) { + this(evaluator, nameMap, false, position); + } + + /** + * @since 2.7 + */ + public ExpressionEval(IExpressionEvaluator evaluator, Map nameMap, boolean unwind, int position) { + this.evaluator = evaluator; + this.nameMap = nameMap; + this.unwind = unwind; + this.position = position; + } + + @Override + public String toString() { + return toString(Object::toString); + } + + @Override + public String toString(Function variableMapping) { + return "extend -"+variableMapping.apply(position)+" = expression "+evaluator.getShortDescription(); + } + + @Override + public ISearchOperationExecutor createExecutor() { + return new Executor(position); + } + + @Override + public List getVariablePositions() { + // XXX not sure if this is the correct implementation to get the affected variable indicies + List variables = new ArrayList<>(); + variables.addAll(nameMap.values()); + return variables; + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExtendBinaryTransitiveClosure.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExtendBinaryTransitiveClosure.java new file mode 100644 index 00000000..1250e84e --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExtendBinaryTransitiveClosure.java @@ -0,0 +1,185 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations.extend; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.Set; +import java.util.function.Function; + +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.IPatternMatcherOperation; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; +import tools.refinery.viatra.runtime.localsearch.operations.util.CallInformation; +import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; + +/** + * Checking for a transitive closure expressed as a local search pattern matcher. The matched pattern must have two + * parameters of the same model type. + * + * @author Zoltan Ujhelyi + * @since 1.7 + * + */ +public abstract class ExtendBinaryTransitiveClosure implements ISearchOperation, IPatternMatcherOperation { + + private class Executor extends SingleValueExtendOperationExecutor { + + public Executor(int position) { + super(position); + } + + @Override + public Iterator getIterator(MatchingFrame frame, ISearchContext context) { + // Note: second parameter is NOT bound during execution, but the first is + IQueryResultProvider matcher = context.getMatcher(information.getCallWithAdornment()); + + Queue seedsToEvaluate = new LinkedList<>(); + final Object seedValue = frame.get(seedPosition); + seedsToEvaluate.add(seedValue); + Set seedsEvaluated = new HashSet<>(); + Set targetsFound = new HashSet<>(); + if (reflexive) { + targetsFound.add(seedValue); + } + + while(!seedsToEvaluate.isEmpty()) { + Object currentValue = seedsToEvaluate.poll(); + seedsEvaluated.add(currentValue); + final Object[] mappedFrame = calculateCallFrame(currentValue); + matcher.getAllMatches(mappedFrame).forEach(match -> { + Object foundTarget = getTarget(match); + targetsFound.add(foundTarget); + if (!seedsEvaluated.contains(foundTarget)) { + seedsToEvaluate.add(foundTarget); + } + }); + } + + return targetsFound.iterator(); + } + + @Override + public ISearchOperation getOperation() { + return ExtendBinaryTransitiveClosure.this; + } + } + + /** + * Calculates the transitive closure of a pattern match in a forward direction (first parameter bound, second + * unbound). + *

+ * Note: In case the call is reflexive, it is expected that the bound parameter already matches the universe type of the call. + * + * @since 1.7 + */ + public static class Forward extends ExtendBinaryTransitiveClosure { + + private Object[] seedFrame = new Object[2]; + + /** + * @since 2.0 + */ + public Forward(CallInformation information, int sourcePosition, int targetPosition, boolean reflexive) { + super(information, sourcePosition, targetPosition, reflexive); + } + + protected Object[] calculateCallFrame(Object seed) { + seedFrame[0] = seed; + seedFrame[1] = null; + return seedFrame; + } + + protected Object getTarget(Tuple frame) { + return frame.get(1); + } + } + + /** + * Calculates the transitive closure of a pattern match in a backward direction (first parameter unbound, second + * bound) + *

+ * Note: In case the call is reflexive, it is expected that the bound parameter already matches the universe type of the call. + * + * @since 2.0 + */ + public static class Backward extends ExtendBinaryTransitiveClosure { + private Object[] seedFrame = new Object[2]; + + /** + * @since 2.0 + */ + public Backward(CallInformation information, int sourcePosition, int targetPosition, boolean reflexive) { + super(information, targetPosition, sourcePosition, reflexive); + } + + protected Object[] calculateCallFrame(Object seed) { + seedFrame[0] = null; + seedFrame[1] = seed; + return seedFrame; + } + + protected Object getTarget(Tuple frame) { + return frame.get(0); + } + } + + private final int seedPosition; + private final int targetPosition; + private final CallInformation information; + private final boolean reflexive; + + /** + * The source position will be matched in the called pattern to the first parameter; while target to the second. + * @since 2.0 + */ + protected ExtendBinaryTransitiveClosure(CallInformation information, int seedPosition, int targetPosition, boolean reflexive) { + this.information = information; + this.seedPosition = seedPosition; + this.targetPosition = targetPosition; + this.reflexive = reflexive; + } + + protected abstract Object[] calculateCallFrame(Object seed); + + protected abstract Object getTarget(Tuple frame); + + @Override + public ISearchOperationExecutor createExecutor() { + return new Executor(targetPosition); + } + + @Override + public String toString() { + return toString(Object::toString); + } + + @Override + public String toString(Function variableMapping) { + String c = information.toString(variableMapping); + int p = c.indexOf('('); + return "extend find " + c.substring(0, p) + "+" + c.substring(p); + } + + @Override + public List getVariablePositions() { + return Arrays.asList(seedPosition, targetPosition); + } + + @Override + public CallInformation getCallInformation() { + return information; + } +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExtendConstant.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExtendConstant.java new file mode 100644 index 00000000..455236f3 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExtendConstant.java @@ -0,0 +1,75 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Marton Bur, Akos Horvath, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations.extend; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.function.Function; + +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; + +/** + * This operation handles constants in search plans by binding a variable to a constant value. Such operations should be + * executed as early as possible during plan execution. + * + * @author Marton Bur + * + */ +public class ExtendConstant implements ISearchOperation { + + private class Executor extends SingleValueExtendOperationExecutor { + + public Executor(int position) { + super(position); + } + + @Override + public Iterator getIterator(MatchingFrame frame, ISearchContext context) { + return Collections.singletonList(value).iterator(); + } + + @Override + public ISearchOperation getOperation() { + return ExtendConstant.this; + } + } + + private final Object value; + private final int position; + + public ExtendConstant(int position, Object value) { + this.position = position; + this.value = value; + } + + @Override + public ISearchOperationExecutor createExecutor() { + return new Executor(position); + } + + @Override + public List getVariablePositions() { + return Arrays.asList(position); + } + + @Override + public String toString() { + return toString(Object::toString); + } + + @Override + public String toString(Function variableMapping) { + return "extend constant -"+variableMapping.apply(position)+"='"+value+"'"; + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExtendPositivePatternCall.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExtendPositivePatternCall.java new file mode 100644 index 00000000..690a3241 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExtendPositivePatternCall.java @@ -0,0 +1,118 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Grill Balázs, IncQueryLabs + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations.extend; + +import java.util.Iterator; +import java.util.List; +import java.util.function.Function; + +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.ExtendOperationExecutor; +import tools.refinery.viatra.runtime.localsearch.operations.IPatternMatcherOperation; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; +import tools.refinery.viatra.runtime.localsearch.operations.util.CallInformation; +import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.VolatileModifiableMaskedTuple; + +/** + * @author Grill Balázs + * @since 1.4 + * + */ +public class ExtendPositivePatternCall implements ISearchOperation, IPatternMatcherOperation { + + private class Executor extends ExtendOperationExecutor { + private final VolatileModifiableMaskedTuple maskedTuple; + + public Executor() { + maskedTuple = new VolatileModifiableMaskedTuple(information.getThinFrameMask()); + } + + @Override + protected Iterator getIterator(MatchingFrame frame, ISearchContext context) { + maskedTuple.updateTuple(frame); + IQueryResultProvider matcher = context.getMatcher(information.getCallWithAdornment()); + return matcher.getAllMatches(information.getParameterMask(), maskedTuple).iterator(); + } + + /** + * @since 2.0 + */ + @Override + protected boolean fillInValue(Tuple result, MatchingFrame frame, ISearchContext context) { + TupleMask mask = information.getFullFrameMask(); + // The first loop clears out the elements from a possible previous iteration + for(int i : information.getFreeParameterIndices()) { + mask.set(frame, i, null); + } + for(int i : information.getFreeParameterIndices()) { + Object oldValue = mask.getValue(frame, i); + Object valueToFill = result.get(i); + if (oldValue != null && !oldValue.equals(valueToFill)){ + // If the inverse map contains more than one values for the same key, it means that these arguments are unified by the caller. + // In this case if the callee assigns different values the frame shall be dropped + return false; + } + mask.set(frame, i, valueToFill); + } + return true; + } + + @Override + protected void cleanup(MatchingFrame frame, ISearchContext context) { + TupleMask mask = information.getFullFrameMask(); + for(int i : information.getFreeParameterIndices()){ + mask.set(frame, i, null); + } + + } + + @Override + public ISearchOperation getOperation() { + return ExtendPositivePatternCall.this; + } + } + + private final CallInformation information; + + /** + * @since 1.7 + */ + public ExtendPositivePatternCall(CallInformation information) { + this.information = information; + } + + @Override + public ISearchOperationExecutor createExecutor() { + return new Executor(); + } + + @Override + public List getVariablePositions() { + return information.getVariablePositions(); + } + + @Override + public String toString() { + return toString(Object::toString); + } + + @Override + public String toString(Function variableMapping) { + return "extend find " + information.toString(variableMapping); + } + + @Override + public CallInformation getCallInformation() { + return information; + } +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExtendToEStructuralFeatureSource.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExtendToEStructuralFeatureSource.java new file mode 100644 index 00000000..04f0a8de --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExtendToEStructuralFeatureSource.java @@ -0,0 +1,112 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Zoltan Ujhelyi, Akos Horvath, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations.extend; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.function.Function; +import java.util.stream.StreamSupport; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EStructuralFeature; +import tools.refinery.viatra.runtime.emf.types.EStructuralFeatureInstancesKey; +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.IIteratingSearchOperation; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.VolatileMaskedTuple; + +/** + * Iterates over all sources of {@link EStructuralFeature} using an {@link IQueryRuntimeContext VIATRA Base indexer}. + * It is assumed that the indexer is initialized for the selected {@link EStructuralFeature}. + * + */ +public class ExtendToEStructuralFeatureSource implements IIteratingSearchOperation { + + private class Executor extends SingleValueExtendOperationExecutor { + + private VolatileMaskedTuple maskedTuple; + + public Executor(int position) { + super(position); + this.maskedTuple = new VolatileMaskedTuple(mask); + } + + + @Override + public Iterator getIterator(MatchingFrame frame, ISearchContext context) { + maskedTuple.updateTuple(frame); + Iterable values = context.getRuntimeContext().enumerateValues(type, indexerMask, maskedTuple); + return StreamSupport.stream(values.spliterator(), false) + .filter(EObject.class::isInstance) + .map(EObject.class::cast) + .iterator(); + } + + @Override + public ISearchOperation getOperation() { + return ExtendToEStructuralFeatureSource.this; + } + } + + private final int sourcePosition; + private final int targetPosition; + private final EStructuralFeature feature; + private final IInputKey type; + private static final TupleMask indexerMask = TupleMask.fromSelectedIndices(2, new int[] {1}); + private final TupleMask mask; + + /** + * @since 1.7 + */ + public ExtendToEStructuralFeatureSource(int sourcePosition, int targetPosition, EStructuralFeature feature, TupleMask mask) { + this.sourcePosition = sourcePosition; + this.targetPosition = targetPosition; + this.feature = feature; + this.mask = mask; + this.type = new EStructuralFeatureInstancesKey(feature); + } + + public EStructuralFeature getFeature() { + return feature; + } + + @Override + public ISearchOperationExecutor createExecutor() { + return new Executor(sourcePosition); + } + + @Override + public String toString() { + return toString(Object::toString); + } + + @Override + public String toString(Function variableMapping) { + return "extend "+feature.getContainerClass().getSimpleName()+"."+feature.getName()+"(-"+variableMapping.apply(sourcePosition)+", +"+variableMapping.apply(targetPosition)+") indexed"; + } + + @Override + public List getVariablePositions() { + return Arrays.asList(sourcePosition, targetPosition); + } + + /** + * @since 1.4 + */ + @Override + public IInputKey getIteratedInputKey() { + return type; + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExtendToEStructuralFeatureTarget.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExtendToEStructuralFeatureTarget.java new file mode 100644 index 00000000..4304fc8d --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/ExtendToEStructuralFeatureTarget.java @@ -0,0 +1,102 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Zoltan Ujhelyi, Akos Horvath, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations.extend; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.function.Function; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EStructuralFeature; +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.exceptions.LocalSearchException; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; + +/** + * Iterates over all sources of {@link EStructuralFeature} + */ +public class ExtendToEStructuralFeatureTarget implements ISearchOperation { + + private class Executor extends SingleValueExtendOperationExecutor { + + public Executor(int position) { + super(position); + } + + @SuppressWarnings("unchecked") + @Override + public Iterator getIterator(MatchingFrame frame, ISearchContext context) { + try { + final EObject value = (EObject) frame.getValue(sourcePosition); + if(! feature.getEContainingClass().isSuperTypeOf(value.eClass()) ){ + // TODO planner should ensure the proper supertype relation + return Collections.emptyIterator(); + } + final Object featureValue = value.eGet(feature); + if (feature.isMany()) { + if (featureValue != null) { + final Collection objectCollection = (Collection) featureValue; + return objectCollection.iterator(); + } else { + return Collections.emptyIterator(); + } + } else { + if (featureValue != null) { + return Collections.singletonList(featureValue).iterator(); + } else { + return Collections.emptyIterator(); + } + } + } catch (ClassCastException e) { + throw new LocalSearchException("Invalid feature source in parameter" + Integer.toString(sourcePosition), e); + } + } + + @Override + public ISearchOperation getOperation() { + return ExtendToEStructuralFeatureTarget.this; + } + } + + private final int sourcePosition; + private final int targetPosition; + private final EStructuralFeature feature; + + public ExtendToEStructuralFeatureTarget(int sourcePosition, int targetPosition, EStructuralFeature feature) { + this.sourcePosition = sourcePosition; + this.targetPosition = targetPosition; + this.feature = feature; + } + + @Override + public String toString() { + return toString(Object::toString); + } + + @Override + public String toString(Function variableMapping) { + return "extend "+feature.getEContainingClass().getName()+"."+feature.getName()+"(+"+variableMapping.apply(sourcePosition)+", -"+ variableMapping.apply(targetPosition) +")"; + } + + @Override + public ISearchOperationExecutor createExecutor() { + return new Executor(targetPosition); + } + + + @Override + public List getVariablePositions() { + return Arrays.asList(sourcePosition, targetPosition); + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/IterateOverChildren.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/IterateOverChildren.java new file mode 100644 index 00000000..10764aea --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/IterateOverChildren.java @@ -0,0 +1,90 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Zoltan Ujhelyi, Akos Horvath, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations.extend; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.function.Function; + +import org.eclipse.emf.ecore.EObject; +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; + +/** + * Iterates all child elements of a selected EObjects. + * + * @author Zoltan Ujhelyi + * + */ +public class IterateOverChildren implements ISearchOperation { + + private class Executor extends SingleValueExtendOperationExecutor { + + public Executor(int position) { + super(position); + } + + @Override + public Iterator getIterator(MatchingFrame frame, ISearchContext context) { + Preconditions.checkState(frame.get(sourcePosition) instanceof EObject, "Only children of EObject elements are supported."); + EObject source = (EObject) frame.get(sourcePosition); + if(transitive) { + return source.eAllContents(); + } else { + return source.eContents().iterator(); + } + } + + @Override + public ISearchOperation getOperation() { + return IterateOverChildren.this; + } + } + + private final int position; + private int sourcePosition; + private final boolean transitive; + + /** + * + * @param position the position of the variable storing the child elements + * @param sourcePosition the position of the variable storing the parent root; must be bound + * @param transitive if true, child elements are iterated over transitively + */ + public IterateOverChildren(int position, int sourcePosition, boolean transitive) { + this.position = position; + this.sourcePosition = sourcePosition; + this.transitive = transitive; + } + + @Override + public String toString() { + return toString(Object::toString); + } + + @Override + public String toString(Function variableMapping) { + return "extend containment +"+variableMapping.apply(sourcePosition)+" <>--> -"+variableMapping.apply(position)+(transitive ? " transitively" : " directly"); + } + + @Override + public ISearchOperationExecutor createExecutor() { + return new Executor(position); + } + + + @Override + public List getVariablePositions() { + return Arrays.asList(position, sourcePosition); + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/IterateOverContainers.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/IterateOverContainers.java new file mode 100644 index 00000000..df7e18c9 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/IterateOverContainers.java @@ -0,0 +1,126 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Zoltan Ujhelyi, Akos Horvath, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations.extend; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.function.Function; + +import org.eclipse.emf.ecore.EObject; +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; + +/** + * Iterates all child elements of a selected EObjects. + * + * @author Zoltan Ujhelyi + * + */ +public class IterateOverContainers implements ISearchOperation { + + /** + * A helper iterator for transitively traversing a parent of an object + */ + private static final class ParentIterator implements Iterator { + private EObject current; + + public ParentIterator(EObject source) { + this.current = source; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + @Override + public EObject next() { + EObject newObject = current.eContainer(); + if (newObject == null) { + throw new NoSuchElementException(String.format("No more parents available for EObject %s", current)); + } + current = newObject; + return current; + } + + @Override + public boolean hasNext() { + return current.eContainer() != null; + } + } + + private class Executor extends SingleValueExtendOperationExecutor { + + public Executor(int position) { + super(position); + } + + @Override + public Iterator getIterator(MatchingFrame frame, ISearchContext context) { + Preconditions.checkState(frame.get(sourcePosition) instanceof EObject, "Only children of EObject elements are supported."); + EObject source = (EObject) frame.get(sourcePosition); + EObject container = source.eContainer(); + if (container == null) { + return Collections.emptyIterator(); + } else if (transitive) { + return new ParentIterator(source); + } else { + return Collections.singleton(container).iterator(); + } + } + + @Override + public ISearchOperation getOperation() { + return IterateOverContainers.this; + } + } + + private final int sourcePosition; + private final int containerPosition; + private final boolean transitive; + + /** + * + * @param containerPosition the position of the variable storing the found parent elements + * @param sourcePosition the position of the variable storing the selected element; must be bound + * @param transitive if false, only the direct container is returned; otherwise all containers + */ + public IterateOverContainers(int containerPosition, int sourcePosition, boolean transitive) { + this.containerPosition = containerPosition; + this.sourcePosition = sourcePosition; + this.transitive = transitive; + } + + + @Override + public ISearchOperationExecutor createExecutor() { + return new Executor(containerPosition); + } + + @Override + public String toString() { + return toString(Object::toString); + } + + @Override + public String toString(Function variableMapping) { + return "extend containment -"+variableMapping.apply(sourcePosition)+" <>--> +"+variableMapping.apply(containerPosition)+(transitive ? " transitively" : " directly"); + } + + @Override + public List getVariablePositions() { + return Arrays.asList(containerPosition, sourcePosition); + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/IterateOverEClassInstances.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/IterateOverEClassInstances.java new file mode 100644 index 00000000..333ed1db --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/IterateOverEClassInstances.java @@ -0,0 +1,97 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Zoltan Ujhelyi, Akos Horvath, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations.extend; + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.function.Function; + +import org.eclipse.emf.ecore.EClass; +import tools.refinery.viatra.runtime.emf.types.EClassTransitiveInstancesKey; +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.IIteratingSearchOperation; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; + +/** + * Iterates all available {@link EClass} instances using an {@link IQueryRuntimeContext VIATRA Base indexer}. It is + * assumed that the base indexer has been registered for the selected type. + * + * @author Zoltan Ujhelyi + * + */ +public class IterateOverEClassInstances implements IIteratingSearchOperation { + + private class Executor extends SingleValueExtendOperationExecutor { + + public Executor(int position) { + super(position); + } + + @Override + public Iterator getIterator(MatchingFrame frame, ISearchContext context) { + return context.getRuntimeContext().enumerateValues(type, indexerMask, Tuples.staticArityFlatTupleOf()).iterator(); + } + + @Override + public ISearchOperation getOperation() { + return IterateOverEClassInstances.this; + } + } + + private final EClass clazz; + private final EClassTransitiveInstancesKey type; + private static final TupleMask indexerMask = TupleMask.empty(1); + private final int position; + + public IterateOverEClassInstances(int position, EClass clazz) { + this.position = position; + this.clazz = clazz; + type = new EClassTransitiveInstancesKey(clazz); + } + + public EClass getClazz() { + return clazz; + } + + + @Override + public ISearchOperationExecutor createExecutor() { + return new Executor(position); + } + + @Override + public String toString() { + return toString(Object::toString); + } + + @Override + public String toString(Function variableMapping) { + return "extend "+clazz.getName()+"(-"+ variableMapping.apply(position)+") indexed"; + } + + @Override + public List getVariablePositions() { + return Collections.singletonList(position); + } + + /** + * @since 1.4 + */ + @Override + public IInputKey getIteratedInputKey() { + return type; + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/IterateOverEDatatypeInstances.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/IterateOverEDatatypeInstances.java new file mode 100644 index 00000000..248a3d27 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/IterateOverEDatatypeInstances.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Zoltan Ujhelyi, Akos Horvath, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations.extend; + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.function.Function; + +import org.eclipse.emf.ecore.EDataType; +import tools.refinery.viatra.runtime.emf.types.EDataTypeInSlotsKey; +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.IIteratingSearchOperation; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; + + +/** + * Iterates over all {@link EDataType} instances using an {@link IQueryRuntimeContext VIATRA Base indexer}. It is + * assumed that the indexer is initialized for the selected {@link EDataType}. + * + */ +public class IterateOverEDatatypeInstances implements IIteratingSearchOperation { + + private class Executor extends SingleValueExtendOperationExecutor { + + public Executor(int position) { + super(position); + } + + @Override + public Iterator getIterator(MatchingFrame frame, ISearchContext context) { + return context.getRuntimeContext().enumerateValues(type, indexerMask, Tuples.staticArityFlatTupleOf()).iterator(); + } + + @Override + public ISearchOperation getOperation() { + return IterateOverEDatatypeInstances.this; + } + } + + private final EDataType dataType; + private final EDataTypeInSlotsKey type; + private static final TupleMask indexerMask = TupleMask.empty(1); + private final int position; + + public IterateOverEDatatypeInstances(int position, EDataType dataType) { + this.position = position; + this.dataType = dataType; + type = new EDataTypeInSlotsKey(dataType); + } + + public EDataType getDataType() { + return dataType; + } + + @Override + public ISearchOperationExecutor createExecutor() { + return new Executor(position); + } + + @Override + public String toString() { + return toString(Object::toString); + } + + @Override + public String toString(Function variableMapping) { + return "extend "+dataType.getName()+"(-"+variableMapping.apply(position)+") indexed"; + } + + @Override + public List getVariablePositions() { + return Collections.singletonList(position); + } + + /** + * @since 1.4 + */ + @Override + public IInputKey getIteratedInputKey() { + return type; + } + + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/IterateOverEStructuralFeatureInstances.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/IterateOverEStructuralFeatureInstances.java new file mode 100644 index 00000000..961939fa --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/IterateOverEStructuralFeatureInstances.java @@ -0,0 +1,115 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Zoltan Ujhelyi, Akos Horvath, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations.extend; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.function.Function; + +import org.eclipse.emf.ecore.EStructuralFeature; +import tools.refinery.viatra.runtime.emf.types.EStructuralFeatureInstancesKey; +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.IIteratingSearchOperation; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; + +/** + * Iterates all available {@link EStructuralFeature} elements using an {@link IQueryRuntimeContext VIATRA Base + * indexer}. It is assumed that the base indexer has been registered for the selected reference type. + * + */ +public class IterateOverEStructuralFeatureInstances implements IIteratingSearchOperation{ + + private class Executor implements ISearchOperationExecutor { + private Iterator it; + + @Override + public void onBacktrack(MatchingFrame frame, ISearchContext context) { + frame.setValue(sourcePosition, null); + frame.setValue(targetPosition, null); + it = null; + } + + @Override + public void onInitialize(MatchingFrame frame, ISearchContext context) { + Iterable tuples = context.getRuntimeContext().enumerateTuples(type, indexerMask, Tuples.staticArityFlatTupleOf()); + + it = tuples.iterator(); + } + + @Override + public boolean execute(MatchingFrame frame, ISearchContext context) { + if (it.hasNext()) { + final Tuple next = it.next(); + frame.setValue(sourcePosition, next.get(0)); + frame.setValue(targetPosition, next.get(1)); + return true; + } else { + return false; + } + } + + @Override + public ISearchOperation getOperation() { + return IterateOverEStructuralFeatureInstances.this; + } + } + + private final EStructuralFeature feature; + private final int sourcePosition; + private final int targetPosition; + private final EStructuralFeatureInstancesKey type; + private static final TupleMask indexerMask = TupleMask.empty(2); + + public IterateOverEStructuralFeatureInstances(int sourcePosition, int targetPosition, EStructuralFeature feature) { + this.sourcePosition = sourcePosition; + this.targetPosition = targetPosition; + this.feature = feature; + type = new EStructuralFeatureInstancesKey(feature); + } + + public EStructuralFeature getFeature() { + return feature; + } + + @Override + public ISearchOperationExecutor createExecutor() { + return new Executor(); + } + + @Override + public String toString() { + return toString(Object::toString); + } + + @Override + public String toString(Function variableMapping) { + return "extend "+feature.getContainerClass().getSimpleName()+"."+feature.getName()+"(-"+variableMapping.apply(sourcePosition)+", -"+variableMapping.apply(targetPosition)+") indexed"; + } + + @Override + public List getVariablePositions() { + return Arrays.asList(sourcePosition, targetPosition); + } + + /** + * @since 1.4 + */ + @Override + public IInputKey getIteratedInputKey() { + return type; + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/SingleValueExtendOperationExecutor.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/SingleValueExtendOperationExecutor.java new file mode 100644 index 00000000..a04ffcca --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/SingleValueExtendOperationExecutor.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations.extend; + +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.ExtendOperationExecutor; + +/** + * @since 2.0 + * @noextend This class is not intended to be subclassed by clients. + */ +public abstract class SingleValueExtendOperationExecutor extends ExtendOperationExecutor { + protected int position; + + /** + * @param position the frame position all values are to be added + */ + public SingleValueExtendOperationExecutor(int position) { + super(); + this.position = position; + } + + @Override + protected final boolean fillInValue(T newValue, MatchingFrame frame, ISearchContext context) { + frame.setValue(position, newValue); + return true; + } + + @Override + protected final void cleanup(MatchingFrame frame, ISearchContext context) { + frame.setValue(position, null); + } +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/nobase/AbstractIteratingExtendOperationExecutor.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/nobase/AbstractIteratingExtendOperationExecutor.java new file mode 100644 index 00000000..954d4c88 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/nobase/AbstractIteratingExtendOperationExecutor.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Grill Balázs, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations.extend.nobase; + +import java.util.Collections; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import org.eclipse.emf.common.notify.Notifier; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.ResourceSet; +import tools.refinery.viatra.runtime.emf.EMFScope; +import tools.refinery.viatra.runtime.localsearch.operations.extend.SingleValueExtendOperationExecutor; + +/** + * This abstract class provides a utility method for extenders to iterate over the given scope. + * + * @author Grill Balázs + * @noextend This class is not intended to be subclassed by clients. + * + */ +abstract class AbstractIteratingExtendOperationExecutor extends SingleValueExtendOperationExecutor { + + private final EMFScope scope; + + public AbstractIteratingExtendOperationExecutor(int position, EMFScope scope) { + super(position); + this.scope = scope; + } + + protected Stream getModelContents() { + return scope.getScopeRoots().stream().map(input -> { + if (input instanceof ResourceSet) { + return ((ResourceSet) input).getAllContents(); + } else if (input instanceof Resource) { + return ((Resource) input).getAllContents(); + } else if (input instanceof EObject) { + return ((EObject) input).eAllContents(); + } + return Collections. emptyIterator(); + }).map(i -> StreamSupport.stream(Spliterators.spliteratorUnknownSize(i, Spliterator.ORDERED), false)) + .flatMap(i -> i); + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/nobase/ExtendToEStructuralFeatureSource.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/nobase/ExtendToEStructuralFeatureSource.java new file mode 100644 index 00000000..fc79640b --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/nobase/ExtendToEStructuralFeatureSource.java @@ -0,0 +1,118 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Marton Bur, Zoltan Ujhelyi, Akos Horvath, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations.extend.nobase; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.function.Function; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.EStructuralFeature; +import tools.refinery.viatra.runtime.base.api.NavigationHelper; +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.exceptions.LocalSearchException; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; +import tools.refinery.viatra.runtime.localsearch.operations.extend.SingleValueExtendOperationExecutor; + +/** + * Iterates over all sources of {@link EStructuralFeature} using an {@link NavigationHelper VIATRA Base indexer}. + * It is assumed that the indexer is initialized for the selected {@link EStructuralFeature}. + * + */ +public class ExtendToEStructuralFeatureSource implements ISearchOperation { + + private class Executor extends SingleValueExtendOperationExecutor { + + private Executor() { + super(sourcePosition); + } + + @SuppressWarnings("unchecked") + @Override + public Iterator getIterator(MatchingFrame frame, ISearchContext context) { + if(!(feature instanceof EReference)){ + throw new LocalSearchException("Without base index, inverse navigation only possible along " + + "EReferences with defined EOpposite."); + } + EReference oppositeFeature = ((EReference)feature).getEOpposite(); + if(oppositeFeature == null){ + throw new LocalSearchException("Feature has no EOpposite, so cannot do inverse navigation " + feature.toString()); + } + try { + final EObject value = (EObject) frame.getValue(targetPosition); + if(! oppositeFeature.getEContainingClass().isSuperTypeOf(value.eClass()) ){ + // TODO planner should ensure the proper supertype relation + return Collections.emptyIterator(); + } + final Object featureValue = value.eGet(oppositeFeature); + if (oppositeFeature.isMany()) { + if (featureValue != null) { + final Collection objectCollection = (Collection) featureValue; + return objectCollection.iterator(); + } else { + return Collections.emptyIterator(); + } + } else { + if (featureValue != null) { + return Collections.singleton(featureValue).iterator(); + } else { + return Collections.emptyIterator(); + } + } + } catch (ClassCastException e) { + throw new LocalSearchException("Invalid feature target in parameter" + Integer.toString(targetPosition), e); + } + } + + @Override + public ISearchOperation getOperation() { + return ExtendToEStructuralFeatureSource.this; + } + } + + private int targetPosition; + private EStructuralFeature feature; + private int sourcePosition; + + public ExtendToEStructuralFeatureSource(int sourcePosition, int targetPosition, EStructuralFeature feature) { + this.sourcePosition = sourcePosition; + this.targetPosition = targetPosition; + this.feature = feature; + } + + public EStructuralFeature getFeature() { + return feature; + } + + @Override + public ISearchOperationExecutor createExecutor() { + return new Executor(); + } + + @Override + public String toString() { + return toString(Object::toString); + } + + @Override + public String toString(Function variableMapping) { + return "extend "+feature.getContainerClass().getSimpleName()+"."+feature.getName()+"(-"+variableMapping.apply(sourcePosition)+", +"+variableMapping.apply(targetPosition)+") iterating"; + } + + @Override + public List getVariablePositions() { + return Arrays.asList(sourcePosition, targetPosition); + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/nobase/IterateOverEClassInstances.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/nobase/IterateOverEClassInstances.java new file mode 100644 index 00000000..0a4c46ec --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/nobase/IterateOverEClassInstances.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Marton Bur, Zoltan Ujhelyi, Akos Horvath, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations.extend.nobase; + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.function.Function; + +import org.eclipse.emf.common.notify.Notifier; +import org.eclipse.emf.ecore.EClass; +import tools.refinery.viatra.runtime.base.api.NavigationHelper; +import tools.refinery.viatra.runtime.emf.EMFScope; +import tools.refinery.viatra.runtime.emf.types.EClassTransitiveInstancesKey; +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.IIteratingSearchOperation; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; +import tools.refinery.viatra.runtime.matchers.context.IInputKey; + +/** + * Iterates all available {@link EClass} instances without using an {@link NavigationHelper VIATRA Base indexer}. + * + * @author Zoltan Ujhelyi + */ +public class IterateOverEClassInstances implements IIteratingSearchOperation { + + private class Executor extends AbstractIteratingExtendOperationExecutor { + + public Executor(int position, EMFScope scope) { + super(position, scope); + } + + @Override + public Iterator getIterator(MatchingFrame frame, ISearchContext context) { + return getModelContents().filter(clazz::isInstance).iterator(); + } + + @Override + public ISearchOperation getOperation() { + return IterateOverEClassInstances.this; + } + } + + private final int position; + private final EClass clazz; + private final EMFScope scope; + + public IterateOverEClassInstances(int position, EClass clazz, EMFScope scope) { + this.position = position; + this.clazz = clazz; + this.scope = scope; + } + + public EClass getClazz() { + return clazz; + } + + @Override + public ISearchOperationExecutor createExecutor() { + return new Executor(position, scope); + } + + @Override + public String toString() { + return toString(Object::toString); + } + + @Override + public String toString(Function variableMapping) { + return "extend "+clazz.getName()+"(-"+ variableMapping.apply(position)+") iterating"; + } + + @Override + public List getVariablePositions() { + return Collections.singletonList(position); + } + + /** + * @since 1.4 + */ + @Override + public IInputKey getIteratedInputKey() { + return new EClassTransitiveInstancesKey(clazz); + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/nobase/IterateOverEDatatypeInstances.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/nobase/IterateOverEDatatypeInstances.java new file mode 100644 index 00000000..999e0a48 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/extend/nobase/IterateOverEDatatypeInstances.java @@ -0,0 +1,123 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Marton Bur, Zoltan Ujhelyi, Akos Horvath, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations.extend.nobase; + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.emf.ecore.EAttribute; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EDataType; +import org.eclipse.emf.ecore.EObject; +import tools.refinery.viatra.runtime.base.api.NavigationHelper; +import tools.refinery.viatra.runtime.emf.EMFScope; +import tools.refinery.viatra.runtime.emf.types.EDataTypeInSlotsKey; +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.IIteratingSearchOperation; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; + +/** + * Iterates over all {@link EDataType} instances without using an {@link NavigationHelper VIATRA Base indexer}. + * + */ +public class IterateOverEDatatypeInstances implements IIteratingSearchOperation { + + private class Executor extends AbstractIteratingExtendOperationExecutor { + + public Executor(int position, EMFScope scope) { + super(position, scope); + } + + @Override + public Iterator getIterator(MatchingFrame frame, final ISearchContext context) { + return getModelContents().filter(EObject.class::isInstance).map(EObject.class::cast) + .map(input -> doGetEAttributes(input.eClass(), context) + .map(attribute -> { + if (attribute.isMany()) { + return ((List) input.eGet(attribute)).stream(); + } else { + Object o = input.eGet(attribute); + return o == null ? Stream.empty() : Stream.of(o); + } + })) + .flatMap(i -> i) + .flatMap(i -> i) + .iterator(); + } + + @Override + public ISearchOperation getOperation() { + return IterateOverEDatatypeInstances.this; + } + } + + private final EDataType dataType; + private final int position; + private final EMFScope scope; + + public IterateOverEDatatypeInstances(int position, EDataType dataType, EMFScope scope) { + this.position = position; + this.dataType = dataType; + this.scope = scope; + } + + protected Stream doGetEAttributes(EClass eclass, ISearchContext context){ + @SuppressWarnings({ "unchecked"}) + Map> cache = context.accessBackendLevelCache(getClass(), Map.class, CollectionsFactory::createMap); + Tuple compositeKey = Tuples.staticArityFlatTupleOf(dataType, eclass); + return cache.computeIfAbsent(compositeKey, k -> + eclass.getEAllAttributes().stream().filter(input -> Objects.equals(input.getEType(), dataType)).collect(Collectors.toSet()) + ).stream(); + } + + public EDataType getDataType() { + return dataType; + } + + @Override + public ISearchOperationExecutor createExecutor() { + return new Executor(position, scope); + } + + @Override + public String toString() { + return toString(Object::toString); + } + + @Override + public String toString(Function variableMapping) { + return "extend "+dataType.getName()+"(-"+variableMapping.apply(position)+") iterating"; + } + + @Override + public List getVariablePositions() { + return Collections.singletonList(position); + } + + /** + * @since 1.4 + */ + @Override + public IInputKey getIteratedInputKey() { + return new EDataTypeInSlotsKey(dataType); + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/generic/GenericTypeCheck.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/generic/GenericTypeCheck.java new file mode 100644 index 00000000..2b189c57 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/generic/GenericTypeCheck.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations.generic; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.CheckOperationExecutor; +import tools.refinery.viatra.runtime.localsearch.operations.IIteratingSearchOperation; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.VolatileMaskedTuple; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; + +/** + * @author Zoltan Ujhelyi + * @since 1.7 + * @noextend This class is not intended to be subclassed by clients. + */ +public class GenericTypeCheck implements ISearchOperation, IIteratingSearchOperation { + + private class Executor extends CheckOperationExecutor { + private VolatileMaskedTuple maskedTuple; + + private Executor() { + this.maskedTuple = new VolatileMaskedTuple(callMask); + } + + @Override + protected boolean check(MatchingFrame frame, ISearchContext context) { + maskedTuple.updateTuple(frame); + return context.getRuntimeContext().containsTuple(type, maskedTuple); + } + + @Override + public ISearchOperation getOperation() { + return GenericTypeCheck.this; + } + } + + private final IInputKey type; + private final List positionList; + private final TupleMask callMask; + + public GenericTypeCheck(IInputKey type, int[] positions, TupleMask callMask) { + this.callMask = callMask; + Preconditions.checkArgument(positions.length == type.getArity(), + "The type %s requires %d parameters, but %d positions are provided", type.getPrettyPrintableName(), + type.getArity(), positions.length); + List modifiablePositionList = new ArrayList<>(); + for (int position : positions) { + modifiablePositionList.add(position); + } + this.positionList = Collections.unmodifiableList(modifiablePositionList); + this.type = type; + } + + @Override + public List getVariablePositions() { + return positionList; + } + + @Override + public ISearchOperationExecutor createExecutor() { + return new Executor(); + } + + @Override + public String toString() { + return toString(Object::toString); + } + + @Override + public String toString(Function variableMapping) { + return "check " + type.getPrettyPrintableName() + "(" + + positionList.stream().map(input -> String.format("+%s", variableMapping.apply(input))).collect(Collectors.joining(", ")) + + ")"; + } + + @Override + public IInputKey getIteratedInputKey() { + return type; + } +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/generic/GenericTypeExtend.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/generic/GenericTypeExtend.java new file mode 100644 index 00000000..dfc3e9ad --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/generic/GenericTypeExtend.java @@ -0,0 +1,145 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations.generic; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.ExtendOperationExecutor; +import tools.refinery.viatra.runtime.localsearch.operations.IIteratingSearchOperation; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.VolatileMaskedTuple; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; + +/** + * @author Zoltan Ujhelyi + * @since 1.7 + * @noextend This class is not intended to be subclassed by clients. + */ +public class GenericTypeExtend implements IIteratingSearchOperation { + + private class Executor extends ExtendOperationExecutor { + private final VolatileMaskedTuple maskedTuple; + + public Executor() { + this.maskedTuple = new VolatileMaskedTuple(callMask); + } + + @Override + protected Iterator getIterator(MatchingFrame frame, ISearchContext context) { + maskedTuple.updateTuple(frame); + return context.getRuntimeContext().enumerateTuples(type, indexerMask, maskedTuple).iterator(); + } + + @Override + protected boolean fillInValue(Tuple newTuple, MatchingFrame frame, ISearchContext context) { + for (Integer position : unboundVariableIndices) { + frame.setValue(position, null); + } + for (int i = 0; i < positions.length; i++) { + Object newValue = newTuple.get(i); + Object oldValue = frame.getValue(positions[i]); + if (oldValue != null && !Objects.equals(oldValue, newValue)) { + // If positions tuple maps more than one values for the same element (e.g. loop), it means that + // these arguments are to unified by the caller. In this case if the callee assigns different values + // the frame shall be considered a failed match + return false; + } + frame.setValue(positions[i], newValue); + } + return true; + } + + @Override + protected void cleanup(MatchingFrame frame, ISearchContext context) { + for (Integer position : unboundVariableIndices) { + frame.setValue(position, null); + } + } + + @Override + public ISearchOperation getOperation() { + return GenericTypeExtend.this; + } + } + + private final IInputKey type; + private final int[] positions; + private final List positionList; + private final Set unboundVariableIndices; + private final TupleMask indexerMask; + private final TupleMask callMask; + + /** + * + * @param type + * the type to execute the extend operation on + * @param positions + * the parameter positions that represent the variables of the input key + * @param unboundVariableIndices + * the set of positions that are bound at the start of the operation + */ + public GenericTypeExtend(IInputKey type, int[] positions, TupleMask callMask, TupleMask indexerMask, Set unboundVariableIndices) { + Preconditions.checkArgument(positions.length == type.getArity(), + "The type %s requires %d parameters, but %d positions are provided", type.getPrettyPrintableName(), + type.getArity(), positions.length); + List modifiablePositionList = new ArrayList<>(); + for (int position : positions) { + modifiablePositionList.add(position); + } + this.positionList = Collections.unmodifiableList(modifiablePositionList); + this.positions = positions; + this.type = type; + + this.unboundVariableIndices = unboundVariableIndices; + this.indexerMask = indexerMask; + this.callMask = callMask; + } + + @Override + public IInputKey getIteratedInputKey() { + return type; + } + + @Override + public ISearchOperationExecutor createExecutor() { + return new Executor(); + } + + @Override + public List getVariablePositions() { + return positionList; + } + + @Override + public String toString() { + return toString(Object::toString); + } + + @Override + public String toString(Function variableMapping) { + return "extend " + type.getPrettyPrintableName() + "(" + + positionList.stream() + .map(input -> String.format("%s%s", unboundVariableIndices.contains(input) ? "-" : "+", variableMapping.apply(input))) + .collect(Collectors.joining(", ")) + + ")"; + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/generic/GenericTypeExtendSingleValue.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/generic/GenericTypeExtendSingleValue.java new file mode 100644 index 00000000..45e4fd0e --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/generic/GenericTypeExtendSingleValue.java @@ -0,0 +1,114 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations.generic; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; + +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.IIteratingSearchOperation; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; +import tools.refinery.viatra.runtime.localsearch.operations.extend.SingleValueExtendOperationExecutor; +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.VolatileMaskedTuple; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; + +/** + * @author Zoltan Ujhelyi + * @since 1.7 + * @noextend This class is not intended to be subclassed by clients. + */ +public class GenericTypeExtendSingleValue implements IIteratingSearchOperation { + + private class Executor extends SingleValueExtendOperationExecutor { + + private final VolatileMaskedTuple maskedTuple; + + public Executor(int position) { + super(position); + this.maskedTuple = new VolatileMaskedTuple(callMask); + } + + @Override + protected Iterator getIterator(MatchingFrame frame, ISearchContext context) { + maskedTuple.updateTuple(frame); + return context.getRuntimeContext().enumerateValues(type, indexerMask, maskedTuple).iterator(); + } + + @Override + public ISearchOperation getOperation() { + return GenericTypeExtendSingleValue.this; + } + } + + private final IInputKey type; + private final List positionList; + private final TupleMask indexerMask; + private final TupleMask callMask; + private final int unboundVariableIndex; + + /** + * + * @param type + * the type to execute the extend operation on + * @param positions + * the parameter positions that represent the variables of the input key + */ + public GenericTypeExtendSingleValue(IInputKey type, int[] positions, TupleMask callMask, TupleMask indexerMask, int unboundVariableIndex) { + Preconditions.checkArgument(positions.length == type.getArity(), + "The type %s requires %d parameters, but %d positions are provided", type.getPrettyPrintableName(), + type.getArity(), positions.length); + List modifiablePositionList = new ArrayList<>(); + for (int position : positions) { + modifiablePositionList.add(position); + } + this.unboundVariableIndex = unboundVariableIndex; + this.positionList = Collections.unmodifiableList(modifiablePositionList); + this.type = type; + + this.callMask = callMask; + this.indexerMask = indexerMask; + } + + @Override + public IInputKey getIteratedInputKey() { + return type; + } + + @Override + public ISearchOperationExecutor createExecutor() { + return new Executor(unboundVariableIndex); + } + + @Override + public List getVariablePositions() { + return positionList; + } + + @Override + public String toString() { + return toString(Object::toString); + } + + @Override + public String toString(Function variableMapping) { + return "extend " + type.getPrettyPrintableName() + "(" + + positionList.stream().map( + input -> String.format("%s%s", Objects.equals(input, unboundVariableIndex) ? "-" : "+", variableMapping.apply(input))) + .collect(Collectors.joining(", ")) + + ")"; + } +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/util/CallInformation.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/util/CallInformation.java new file mode 100644 index 00000000..b141a7b0 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/operations/util/CallInformation.java @@ -0,0 +1,186 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.operations.util; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import tools.refinery.viatra.runtime.localsearch.matcher.CallWithAdornment; +import tools.refinery.viatra.runtime.localsearch.matcher.MatcherReference; +import tools.refinery.viatra.runtime.matchers.psystem.IQueryReference; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.PatternCallBasedDeferred; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.BinaryReflexiveTransitiveClosure; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.BinaryTransitiveClosure; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.PositivePatternCall; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; + +/** + * This class stores a precompiled version of call-related metadata and masks for local search operations + * + * @author Zoltan Ujhelyi + * @since 1.7 + */ +public final class CallInformation { + + private final TupleMask fullFrameMask; + private final TupleMask thinFrameMask; + private final TupleMask parameterMask; + private final int[] freeParameterIndices; + + private final Map mapping = new HashMap<>(); + private final Set adornment = new HashSet<>(); + private final PQuery referredQuery; + private final MatcherReference matcherReference; + private final IQueryReference call; + private CallWithAdornment callWithAdornment; + + public static CallInformation create(PatternCallBasedDeferred constraint, Map variableMapping, Set bindings) { + return new CallInformation(constraint.getActualParametersTuple(), constraint, bindings, variableMapping); + } + + public static CallInformation create(PositivePatternCall pCall, Map variableMapping, Set bindings) { + return new CallInformation(pCall.getVariablesTuple(), pCall, bindings, variableMapping); + } + + public static CallInformation create(BinaryTransitiveClosure constraint, Map variableMapping, Set bindings) { + return new CallInformation(constraint.getVariablesTuple(), constraint, bindings, variableMapping); + } + + /** + * @since 2.0 + */ + public static CallInformation create(BinaryReflexiveTransitiveClosure constraint, Map variableMapping, Set bindings) { + return new CallInformation(constraint.getVariablesTuple(), constraint, bindings, variableMapping); + } + + private CallInformation(Tuple actualParameters, IQueryReference call, final Set bindings, + Map variableMapping) { + this.call = call; + this.referredQuery = call.getReferredQuery(); + int keySize = actualParameters.getSize(); + List parameterMaskIndices = new ArrayList<>(); + int[] fullParameterMaskIndices = new int[keySize]; + for (int i = 0; i < keySize; i++) { + PParameter symbolicParameter = referredQuery.getParameters().get(i); + PVariable parameter = (PVariable) actualParameters.get(i); + Integer originalFrameIndex = variableMapping.get(parameter); + mapping.put(symbolicParameter, originalFrameIndex); + fullParameterMaskIndices[i] = originalFrameIndex; + if (bindings.contains(originalFrameIndex)) { + parameterMaskIndices.add(originalFrameIndex); + adornment.add(symbolicParameter); + } + } + + thinFrameMask = TupleMask.fromSelectedIndices(variableMapping.size(), parameterMaskIndices); + fullFrameMask = TupleMask.fromSelectedIndices(variableMapping.size(), fullParameterMaskIndices); + + // This second iteration is necessary as we don't know beforehand the number of bound parameters + int[] boundParameterIndices = new int[adornment.size()]; + int boundIndex = 0; + freeParameterIndices = new int[keySize - adornment.size()]; + int freeIndex = 0; + for (int i = 0; i < keySize; i++) { + if (bindings.contains(variableMapping.get(actualParameters.get(i)))) { + boundParameterIndices[boundIndex] = i; + boundIndex++; + } else { + freeParameterIndices[freeIndex] = i; + freeIndex++; + } + } + parameterMask = TupleMask.fromSelectedIndices(keySize, boundParameterIndices); + callWithAdornment = new CallWithAdornment(call, adornment); + matcherReference = callWithAdornment.getMatcherReference(); + } + + /** + * Returns a mask describing how the bound variables of a Matching Frame are mapped to parameter indexes + */ + public TupleMask getThinFrameMask() { + return thinFrameMask; + } + + /** + * Returns a mask describing how all variables of a Matching Frame are mapped to parameter indexes + */ + public TupleMask getFullFrameMask() { + return fullFrameMask; + } + + /** + * Returns a mask describing the adornment the called pattern uses + */ + public TupleMask getParameterMask() { + return parameterMask; + } + + public MatcherReference getReference() { + return matcherReference; + } + + /** + * @since 2.1 + */ + public IQueryReference getCall() { + return call; + } + + /** + * @since 2.1 + */ + public CallWithAdornment getCallWithAdornment() { + return callWithAdornment; + } + + /** + * Returns the parameter indices that are unbound before the call + */ + public int[] getFreeParameterIndices() { + return freeParameterIndices; + } + + public List getVariablePositions() { + List variables = new ArrayList<>(mapping.size()); + for(PParameter p : referredQuery.getParameters()){ + variables.add(mapping.get(p)); + } + return variables; + } + + + + @Override + public String toString() { + return toString(Object::toString); + } + + /** + * @since 2.0 + */ + public String toString(Function variableMapping) { + return referredQuery.getFullyQualifiedName() + "(" + + referredQuery.getParameters().stream().map( + input -> (adornment.contains(input) ? "+" : "-") + variableMapping.apply(mapping.get(input))) + .collect(Collectors.joining(",")) + + ")"; + } + + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/IPlanDescriptor.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/IPlanDescriptor.java new file mode 100644 index 00000000..987996c9 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/IPlanDescriptor.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Grill Balázs, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.plan; + +import java.util.Collection; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; + +/** + * Denotes an executable plan + * + * @author Grill Balázs + * @since 1.4 + * + */ +public interface IPlanDescriptor { + + /** + * The query which this plan implements + */ + public PQuery getQuery(); + + /** + * The executable search plans for each body in the query + * @since 2.0 + */ + public Collection getPlan(); + + /** + * The set of parameters this plan assumes to be bound + */ + public Set getAdornment(); + + /** + * The collection of {@link IInputKey}s which needs to be iterated during the execution of this plan. For optimal + * performance, instances of these keys might be indexed. + */ + public Set getIteratedKeys(); + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/IPlanProvider.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/IPlanProvider.java new file mode 100644 index 00000000..b74282ca --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/IPlanProvider.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Grill Balázs, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.plan; + +import tools.refinery.viatra.runtime.localsearch.matcher.MatcherReference; +import tools.refinery.viatra.runtime.localsearch.matcher.integration.LocalSearchHints; +import tools.refinery.viatra.runtime.localsearch.planner.compiler.IOperationCompiler; +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; +import tools.refinery.viatra.runtime.matchers.backend.ResultProviderRequestor; +import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; + +/** + * @author Grill Balázs + * @since 1.4 + * @noreference This interface is not intended to be referenced by clients. + */ +public interface IPlanProvider { + + /** + * @throws ViatraQueryRuntimeException + * @since 2.1 + */ + public IPlanDescriptor getPlan(IQueryBackendContext backend, IOperationCompiler compiler, + ResultProviderRequestor resultProviderRequestor, + LocalSearchHints configuration, MatcherReference key); + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/PlanDescriptor.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/PlanDescriptor.java new file mode 100644 index 00000000..a5565546 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/PlanDescriptor.java @@ -0,0 +1,84 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Grill Balázs, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.plan; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import tools.refinery.viatra.runtime.localsearch.operations.IIteratingSearchOperation; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; + +/** + * @author Grill Balázs + * @since 1.4 + * + */ +public class PlanDescriptor implements IPlanDescriptor { + + private final PQuery pquery; + private final List plan; + private final Set adornment; + private Set iteratedKeys = null; + + public PlanDescriptor(PQuery pquery, Collection plan, Set adornment) { + this.pquery = pquery; + this.plan = new ArrayList<>(plan); + this.adornment = adornment; + } + + @Override + public PQuery getQuery() { + return pquery; + } + + @Override + public Collection getPlan() { + return plan; + } + + @Override + public Set getAdornment() { + return adornment; + } + + @Override + public Set getIteratedKeys() { + if (iteratedKeys == null){ + Set keys = new HashSet<>(); + for(SearchPlanForBody bodyPlan : plan){ + for(ISearchOperation operation : bodyPlan.getCompiledOperations()){ + if (operation instanceof IIteratingSearchOperation){ + keys.add(((IIteratingSearchOperation) operation).getIteratedInputKey()); + } + } + } + iteratedKeys = Collections.unmodifiableSet(keys); + } + return iteratedKeys; + } + + @Override + public String toString() { + return new StringBuilder().append("Plan for ").append(pquery.getFullyQualifiedName()).append("(") + .append(adornment.stream().map(PParameter::getName).collect(Collectors.joining(","))) + .append("{") + .append(plan.stream().map(Object::toString).collect(Collectors.joining("}\n{"))) + .append("}") + .toString(); + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/SearchPlan.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/SearchPlan.java new file mode 100644 index 00000000..3159f707 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/SearchPlan.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.plan; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; + +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; + +/** + * A SearchPlan stores a collection of SearchPlanOperations for a fixed order of variables. + * + * @author Zoltan Ujhelyi + * + */ +public class SearchPlan { + + private final List operations; + private final Map variableMapping; + private final TupleMask parameterMask; + private final PBody body; + + /** + * @since 2.0 + */ + public SearchPlan(PBody body, List operations, TupleMask parameterMask, Map variableMapping) { + this.body = body; + this.operations = Collections.unmodifiableList(new ArrayList<>(operations)); + this.parameterMask = parameterMask; + this.variableMapping = Collections.unmodifiableMap(variableMapping.entrySet().stream() + .collect(Collectors.toMap(Entry::getValue, Entry::getKey))); + } + + + /** + * Returns an immutable list of operations stored in the plan. + * @return the operations + */ + public List getOperations() { + return operations; + } + + /** + * Returns an immutable map of variable mappings for the plan + * @since 2.0 + */ + public Map getVariableMapping() { + return variableMapping; + } + + /** + * Returns the index of a given operation in the plan + * @since 2.0 + */ + public int getOperationIndex(ISearchOperation operation) { + return operations.indexOf(operation); + } + + /** + * @since 2.0 + */ + public TupleMask getParameterMask() { + return parameterMask; + } + + /** + * @since 2.0 + */ + public PBody getSourceBody() { + return body; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + + sb.append("{\n"); + for(ISearchOperation operation : this.getOperations()){ + sb.append("\t"); + sb.append(operation); + sb.append("\n"); + } + sb.append("}"); + return sb.toString(); + } +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/SearchPlanExecutor.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/SearchPlanExecutor.java new file mode 100644 index 00000000..4a4e2450 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/SearchPlanExecutor.java @@ -0,0 +1,210 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Akos Horvath, Gergely Varro Zoltan Ujhelyi and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + + package tools.refinery.viatra.runtime.localsearch.plan; + + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.stream.Collectors; + +import org.apache.log4j.Logger; +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.exceptions.LocalSearchException; +import tools.refinery.viatra.runtime.localsearch.matcher.ILocalSearchAdaptable; +import tools.refinery.viatra.runtime.localsearch.matcher.ILocalSearchAdapter; +import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation.ISearchOperationExecutor; +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; + +/** + * A search plan executor is used to execute {@link SearchPlan} instances. + * @noinstantiate This class is not intended to be instantiated by clients. + */ +public class SearchPlanExecutor implements ILocalSearchAdaptable { + + private int currentOperation; + private final List operations; + private final SearchPlan plan; + private final ISearchContext context; + private final List adapters = new CopyOnWriteArrayList<>(); + + /** + * @since 2.0 + */ + public Map getVariableMapping() { + return plan.getVariableMapping(); + } + + public int getCurrentOperation() { + return currentOperation; + } + + public SearchPlan getSearchPlan() { + return plan; + } + + /** + * @since 1.7 + */ + public TupleMask getParameterMask() { + return plan.getParameterMask(); + } + + @Override + public void addAdapters(List adapters) { + for(ILocalSearchAdapter adapter : adapters){ + if (!this.adapters.contains(adapter)){ + this.adapters.add(adapter); + adapter.adapterRegistered(this); + } + } + } + + @Override + public void removeAdapters(List adapters) { + for (ILocalSearchAdapter adapter : adapters) { + if (this.adapters.remove(adapter)){ + adapter.adapterUnregistered(this); + } + } + } + + /** + * @since 2.0 + */ + public SearchPlanExecutor(SearchPlan plan, ISearchContext context) { + Preconditions.checkArgument(context != null, "Context cannot be null"); + this.plan = plan; + this.context = context; + operations = plan.getOperations().stream().map(ISearchOperation::createExecutor).collect(Collectors.toList()); + this.currentOperation = -1; + } + + + private void init(MatchingFrame frame) { + if (currentOperation == -1) { + currentOperation++; + ISearchOperationExecutor operation = operations.get(currentOperation); + if (!adapters.isEmpty()){ + for (ILocalSearchAdapter adapter : adapters) { + adapter.executorInitializing(plan, frame); + } + } + operation.onInitialize(frame, context); + } else if (currentOperation == operations.size()) { + currentOperation--; + } else { + throw new LocalSearchException(LocalSearchException.PLAN_EXECUTION_ERROR); + } + } + + + /** + * Calculates the cost of the search plan. + */ + public double cost() { + /* default generated stub */ + return 0.0; + } + + /** + * @throws ViatraQueryRuntimeException + */ + public boolean execute(MatchingFrame frame) { + int upperBound = operations.size() - 1; + init(frame); + operationSelected(frame, currentOperation, false); + while (currentOperation >= 0 && currentOperation <= upperBound) { + if (operations.get(currentOperation).execute(frame, context)) { + operationExecuted(frame, currentOperation, true); + currentOperation++; + operationSelected(frame, currentOperation, false); + if (currentOperation <= upperBound) { + ISearchOperationExecutor operation = operations.get(currentOperation); + operation.onInitialize(frame, context); + } + } else { + operationExecuted(frame, currentOperation, false); + ISearchOperationExecutor operation = operations.get(currentOperation); + operation.onBacktrack(frame, context); + currentOperation--; + operationSelected(frame, currentOperation, true); + } + } + boolean matchFound = currentOperation > upperBound; + if (matchFound && !adapters.isEmpty()) { + for (ILocalSearchAdapter adapter : adapters) { + adapter.matchFound(plan, frame); + } + } + return matchFound; + } + + public void resetPlan() { + currentOperation = -1; + } + + public void printDebugInformation() { + for (int i = 0; i < operations.size(); i++) { + Logger.getRootLogger().debug("[" + i + "]\t" + operations.get(i).toString()); + } + } + + private void operationExecuted(MatchingFrame frame, int operationIndex, boolean isSuccessful) { + if (!adapters.isEmpty()){ + for (ILocalSearchAdapter adapter : adapters) { + adapter.operationExecuted(plan, operations.get(operationIndex).getOperation(), frame, isSuccessful); + } + } + } + + private void operationSelected(MatchingFrame frame, int operationIndex, boolean isBacktrack) { + if (!adapters.isEmpty() && operationIndex >= 0 && operationIndex < operations.size()){ + for (ILocalSearchAdapter adapter : adapters) { + adapter.operationSelected(plan, operations.get(operationIndex).getOperation(), frame, isBacktrack); + } + } + } + + public ISearchContext getContext() { + return context; + } + + @Override + public List getAdapters() { + return Collections.unmodifiableList(this.adapters); + } + + @Override + public void addAdapter(ILocalSearchAdapter adapter) { + addAdapters(Collections.singletonList(adapter)); + } + + @Override + public void removeAdapter(ILocalSearchAdapter adapter) { + removeAdapters(Collections.singletonList(adapter)); + } + + @Override + public String toString() { + if (operations == null) { + return "Unspecified plan"; + } else { + return operations.stream().map(Object::toString).collect(Collectors.joining("\n")); + } + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/SearchPlanForBody.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/SearchPlanForBody.java new file mode 100644 index 00000000..e0300da4 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/SearchPlanForBody.java @@ -0,0 +1,115 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.plan; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import tools.refinery.viatra.runtime.localsearch.matcher.CallWithAdornment; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; +import tools.refinery.viatra.runtime.matchers.planning.SubPlan; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; + +/** + * This class is responsible for storing the results of the planner and operation compiler for a selected body. + * @since 2.0 + * @noinstantiate This class is not intended to be instantiated by clients. + */ +public class SearchPlanForBody { + + private final PBody body; + private final Map variableKeys; + private final int[] parameterKeys; + private final SubPlan plan; + private final List compiledOperations; + private final Collection dependencies; + private final double cost; + private final Object internalRepresentation; + + /** + * @since 2.1 + */ + public SearchPlanForBody(PBody body, Map variableKeys, + SubPlan plan, List compiledOperations, Collection dependencies, + Object internalRepresentation, double cost) { + super(); + this.body = body; + this.variableKeys = variableKeys; + this.plan = plan; + this.internalRepresentation = internalRepresentation; + this.cost = cost; + List parameters = body.getSymbolicParameterVariables(); + parameterKeys = new int[parameters.size()]; + for (int i=0; i(compiledOperations.size()+1); + this.compiledOperations.addAll(compiledOperations); + + this.dependencies = new ArrayList<>(dependencies); + } + + public PBody getBody() { + return body; + } + + public Map getVariableKeys() { + return variableKeys; + } + + public int[] getParameterKeys() { + return Arrays.copyOf(parameterKeys, parameterKeys.length); + } + + public List getCompiledOperations() { + return compiledOperations; + } + + public SubPlan getPlan() { + return plan; + } + + public Collection getDependencies() { + return dependencies; + } + + public TupleMask calculateParameterMask() { + return TupleMask.fromSelectedIndices(variableKeys.size(), parameterKeys); + } + + @Override + public String toString() { + return compiledOperations.stream().map(Object::toString).collect(Collectors.joining("\n")); + } + + /** + * @since 2.1 + */ + public double getCost() { + return cost; + } + + /** + * @return The internal representation of the search plan, if any, for traceability + * @since 2.1 + */ + public Object getInternalRepresentation() { + return internalRepresentation; + } + + + + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/SimplePlanProvider.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/SimplePlanProvider.java new file mode 100644 index 00000000..ed31bcb0 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/plan/SimplePlanProvider.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Grill Balázs, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.plan; + +import java.util.Collection; + +import org.apache.log4j.Logger; +import tools.refinery.viatra.runtime.localsearch.matcher.MatcherReference; +import tools.refinery.viatra.runtime.localsearch.matcher.integration.LocalSearchHints; +import tools.refinery.viatra.runtime.localsearch.planner.LocalSearchPlanner; +import tools.refinery.viatra.runtime.localsearch.planner.compiler.IOperationCompiler; +import tools.refinery.viatra.runtime.matchers.backend.ResultProviderRequestor; +import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; + +/** + * A plan provider implementation which caches previously calculated plans to avoid re-planning for the same adornment + * + * @author Grill Balázs + * @since 1.7 + * + */ +public class SimplePlanProvider implements IPlanProvider { + + private final Logger logger; + + public SimplePlanProvider(Logger logger) { + this.logger = logger; + } + + @Override + public IPlanDescriptor getPlan(IQueryBackendContext backend, IOperationCompiler compiler, + final ResultProviderRequestor resultRequestor, + final LocalSearchHints configuration, MatcherReference key) { + + LocalSearchPlanner planner = new LocalSearchPlanner(backend, compiler, logger, configuration, resultRequestor); + + Collection plansForBodies = planner.plan(key.getQuery(), key.getAdornment()); + + IPlanDescriptor plan = new PlanDescriptor(key.getQuery(), plansForBodies, key.getAdornment()); + return plan; + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/ILocalSearchPlanner.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/ILocalSearchPlanner.java new file mode 100644 index 00000000..dfd9a3c8 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/ILocalSearchPlanner.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.planner; + +import java.util.Collection; +import java.util.Set; + +import tools.refinery.viatra.runtime.localsearch.plan.SearchPlanForBody; +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; + +/** + * @author Zoltan Ujhelyi + * @since 1.7 + */ +public interface ILocalSearchPlanner { + + /** + * Creates executable plans for the provided query. It is required to call one of the + * initializePlanner() methods before calling this method. + * + * @param querySpec + * @param boundParameters + * a set of bound parameters + * @return a mapping between ISearchOperation list and a mapping, that holds a PVariable-Integer mapping for the + * list of ISearchOperations + * @throws ViatraQueryRuntimeException + */ + Collection plan(PQuery querySpec, Set boundParameters); + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/ISearchPlanCodeGenerator.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/ISearchPlanCodeGenerator.java new file mode 100644 index 00000000..72218337 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/ISearchPlanCodeGenerator.java @@ -0,0 +1,23 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Marton Bur, Akos Horvath, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.planner; + +import java.util.List; + +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; + +/** + * @author Marton Bur + * + */ +public interface ISearchPlanCodeGenerator { + + void compile(List> plans); + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/LocalSearchPlanner.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/LocalSearchPlanner.java new file mode 100644 index 00000000..f44be655 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/LocalSearchPlanner.java @@ -0,0 +1,140 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Marton Bur, Akos Horvath, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.planner; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.log4j.Logger; +import tools.refinery.viatra.runtime.localsearch.matcher.integration.LocalSearchHints; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; +import tools.refinery.viatra.runtime.localsearch.plan.SearchPlanForBody; +import tools.refinery.viatra.runtime.localsearch.planner.compiler.IOperationCompiler; +import tools.refinery.viatra.runtime.matchers.backend.ResultProviderRequestor; +import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.planning.SubPlan; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.ExportedParameter; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.psystem.rewriters.PBodyNormalizer; +import tools.refinery.viatra.runtime.matchers.psystem.rewriters.PDisjunctionRewriter; +import tools.refinery.viatra.runtime.matchers.psystem.rewriters.PDisjunctionRewriterCacher; +import tools.refinery.viatra.runtime.matchers.psystem.rewriters.PQueryFlattener; + +/** + * + * @author Marton Bur + * @noreference This class is not intended to be referenced by clients. + */ +public class LocalSearchPlanner implements ILocalSearchPlanner { + + // Externally set tools for planning + private final PDisjunctionRewriter preprocessor; + private final LocalSearchRuntimeBasedStrategy plannerStrategy; + private final IQueryRuntimeContext runtimeContext; + private final LocalSearchHints configuration; + private final IOperationCompiler operationCompiler; + private final IQueryBackendContext context; + private final ResultProviderRequestor resultRequestor; + + /** + * @param resultRequestor + * @since 1.7 + */ + public LocalSearchPlanner(IQueryBackendContext backendContext, IOperationCompiler compiler, Logger logger, + final LocalSearchHints configuration, ResultProviderRequestor resultRequestor) + { + + this.runtimeContext = backendContext.getRuntimeContext(); + this.configuration = configuration; + this.operationCompiler = compiler; + this.resultRequestor = resultRequestor; + PQueryFlattener flattener = new PQueryFlattener(configuration.getFlattenCallPredicate()); + /* + * TODO https://bugs.eclipse.org/bugs/show_bug.cgi?id=439358: The normalizer is initialized with the false + * parameter to turn off unary constraint elimination to work around an issue related to plan ordering: the + * current implementation of the feature target checking operations expect that the source types were checked + * before. However, this causes duplicate constraint checks in the search plan that might affect performance + * negatively. + */ + PBodyNormalizer normalizer = new PBodyNormalizer(runtimeContext.getMetaContext()) { + + @Override + protected boolean shouldCalculateImpliedTypes(PQuery query) { + return false; + } + }; + preprocessor = new PDisjunctionRewriterCacher(flattener, normalizer); + + plannerStrategy = new LocalSearchRuntimeBasedStrategy(); + + context = backendContext; + } + + /** + * Creates executable plans for the provided query. It is required to call one of the + * initializePlanner() methods before calling this method. + * + * @param querySpec + * @param boundParameters + * a set of bound parameters + * @return a mapping between ISearchOperation list and a mapping, that holds a PVariable-Integer mapping for the + * list of ISearchOperations + */ + @Override + public Collection plan(PQuery querySpec, Set boundParameters) { + // 1. Preparation + preprocessor.setTraceCollector(configuration.getTraceCollector()); + Set normalizedBodies = preprocessor.rewrite(querySpec.getDisjunctBodies()).getBodies(); + + List plansForBodies = new ArrayList<>(normalizedBodies.size()); + + for (PBody normalizedBody : normalizedBodies) { + // 2. Plan creation + // Context has matchers for the referred Queries (IQuerySpecifications) + Set boundVariables = calculatePatternAdornmentForPlanner(boundParameters, normalizedBody); + PlanState searchPlanInternal = plannerStrategy.plan(normalizedBody, boundVariables, context, resultRequestor, configuration); + SubPlan plan = plannerStrategy.convertPlan(boundVariables, searchPlanInternal); + // 3. PConstraint -> POperation compilation step + // * Pay extra caution to extend operations, when more than one variables are unbound + List compiledOperations = operationCompiler.compile(plan, boundParameters); + // Store the variable mappings for the plans for debug purposes (traceability information) + SearchPlanForBody compiledPlan = new SearchPlanForBody(normalizedBody, + operationCompiler.getVariableMappings(), plan, compiledOperations, + operationCompiler.getDependencies(), + searchPlanInternal, searchPlanInternal.getCost()); + + plansForBodies.add(compiledPlan); + } + + return plansForBodies; + } + + private Set calculatePatternAdornmentForPlanner(Set boundParameters, PBody normalizedBody) { + Map parameterMapping = new HashMap<>(); + for (ExportedParameter constraint : normalizedBody.getSymbolicParameters()) { + parameterMapping.put(constraint.getPatternParameter(), constraint.getParameterVariable()); + } + Set boundVariables = new HashSet<>(); + for (PParameter parameter : boundParameters) { + PVariable mappedParameter = parameterMapping.get(parameter); + boundVariables.add(mappedParameter); + } + return boundVariables; + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/LocalSearchRuntimeBasedStrategy.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/LocalSearchRuntimeBasedStrategy.java new file mode 100644 index 00000000..1bebe37e --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/LocalSearchRuntimeBasedStrategy.java @@ -0,0 +1,257 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Marton Bur, Akos Horvath, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.planner; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import tools.refinery.viatra.runtime.localsearch.matcher.integration.LocalSearchHints; +import tools.refinery.viatra.runtime.localsearch.planner.cost.ICostFunction; +import tools.refinery.viatra.runtime.localsearch.planner.util.OperationCostComparator; +import tools.refinery.viatra.runtime.matchers.backend.ResultProviderRequestor; +import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; +import tools.refinery.viatra.runtime.matchers.planning.SubPlan; +import tools.refinery.viatra.runtime.matchers.planning.SubPlanFactory; +import tools.refinery.viatra.runtime.matchers.planning.operations.PApply; +import tools.refinery.viatra.runtime.matchers.planning.operations.PProject; +import tools.refinery.viatra.runtime.matchers.planning.operations.PStart; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.util.Sets; + +/** + * This class contains the logic for local search plan calculation based on costs of the operations. + * Its name refers to the fact that the strategy tries to use as much information as available about + * the model on which the matching is initiated. When no runtime info is available, it falls back to + * the information available from the metamodel durint operation cost calculation. + * + * The implementation is based on the paper "Gergely Varró, Frederik Deckwerth, Martin Wieber, and Andy Schürr: + * An algorithm for generating model-sensitive search plans for pattern matching on EMF models" + * (DOI: 10.1007/s10270-013-0372-2) + * + * @author Marton Bur + * @noreference This class is not intended to be referenced by clients. + */ +public class LocalSearchRuntimeBasedStrategy { + + private final OperationCostComparator infoComparator = new OperationCostComparator(); + + + /** + * Converts a plan to the standard format + */ + protected SubPlan convertPlan(Set initialBoundVariables, PlanState searchPlan) { + PBody pBody; + pBody = searchPlan.getAssociatedPBody(); + + // Create a starting plan + SubPlanFactory subPlanFactory = new SubPlanFactory(pBody); + + // We assume that the adornment (now the bound variables) is previously set + SubPlan plan = subPlanFactory.createSubPlan(new PStart(initialBoundVariables)); + + List operations = searchPlan.getOperations(); + for (PConstraintInfo pConstraintPlanInfo : operations) { + PConstraint pConstraint = pConstraintPlanInfo.getConstraint(); + plan = subPlanFactory.createSubPlan(new PApply(pConstraint), plan); + } + + return subPlanFactory.createSubPlan(new PProject(pBody.getSymbolicParameterVariables()), plan); + } + + /** + * The implementation of a local search-based algorithm to create a search plan for a flattened (and normalized) + * PBody + * @param pBody for which the plan is to be created + * @param initialBoundVariables variables that are known to have already assigned values + * @param context the backend context + * @param resultProviderRequestor requestor for accessing result providers of called patterns + * @param configuration the planner configuration + * @return the complete search plan for the given {@link PBody} + * @since 2.1 + */ + protected PlanState plan(PBody pBody, Set initialBoundVariables, + IQueryBackendContext context, final ResultProviderRequestor resultProviderRequestor, + LocalSearchHints configuration) { + final ICostFunction costFunction = configuration.getCostFunction(); + PConstraintInfoInferrer pConstraintInfoInferrer = new PConstraintInfoInferrer( + configuration.isUseBase(), context, resultProviderRequestor, costFunction::apply); + + // Create mask infos + Set constraintSet = pBody.getConstraints(); + List constraintInfos = + pConstraintInfoInferrer.createPConstraintInfos(constraintSet); + + // Calculate the characteristic function + // The characteristic function tells whether a given adornment is backward reachable from the (B)* state, where + // each variable is bound. + // The characteristic function is represented as a set of set of variables + // TODO this calculation is not not implemented yet, thus the contents of the returned set is not considered later + List> reachableBoundVariableSets = reachabilityAnalysis(pBody, constraintInfos); + int k = configuration.getRowCount(); + PlanState searchPlan = calculateSearchPlan(pBody, initialBoundVariables, k, reachableBoundVariableSets, constraintInfos); + return searchPlan; + } + + private PlanState calculateSearchPlan(PBody pBody, Set initialBoundVariables, int k, + List> reachableBoundVariableSets, List allMaskInfos) { + + List allPotentialExtendInfos = new ArrayList<>(); + List allPotentialCheckInfos = new ArrayList<>(); + Map> checkOpsByVariables = new HashMap<>(); + Map> extendOpsByBoundVariables = new HashMap<>(); + + for (PConstraintInfo op : allMaskInfos) { + if (op.getFreeVariables().isEmpty()) { // CHECK + allPotentialCheckInfos.add(op); + } else { // EXTEND + allPotentialExtendInfos.add(op); + for (PVariable variable : op.getBoundVariables()) { + extendOpsByBoundVariables.computeIfAbsent(variable, v -> new ArrayList<>()).add(op); + } + } + } + // For CHECKs only, we must start from lists that are ordered by the cost of the constraint application + Collections.sort(allPotentialCheckInfos, infoComparator); // costs are eagerly needed for check ops + for (PConstraintInfo op : allPotentialCheckInfos) { + for (PVariable variable : op.getBoundVariables()) { + checkOpsByVariables.computeIfAbsent(variable, v -> new ArrayList<>()).add(op); + } + } + // costs are not needed for extend ops until they are first applied (TODO make cost computaiton on demand) + + + // rename for better understanding + Set boundVariables = initialBoundVariables; + Set freeVariables = Sets.difference(pBody.getUniqueVariables(), initialBoundVariables); + + int variableCount = pBody.getUniqueVariables().size(); + int n = freeVariables.size(); + + List> stateTable = initializeStateTable(k, n); + + // Set initial state: begin with an empty operation list + PlanState initialState = new PlanState(pBody, boundVariables); + + // Initial state creation, categorizes all operations; add present checks to operationsList + initialState.updateExtends(allPotentialExtendInfos); + initialState.applyChecks(allPotentialCheckInfos); + stateTable.get(n).add(0, initialState); + + // stateTable.get(0) will contain the states with adornment B* + for (int i = n; i > 0; i--) { + for (int j = 0; j < k && j < stateTable.get(i).size(); j++) { + PlanState currentState = stateTable.get(i).get(j); + + for (PConstraintInfo constraintInfo : currentState.getPresentExtends()) { + // for each present EXTEND operation + PlanState newState = calculateNextState(currentState, constraintInfo); + // also eagerly perform any CHECK operations that become applicable (extends still deferred) + newState.applyChecksBasedOnDelta(checkOpsByVariables); + + if(currentState.getBoundVariables().size() == newState.getBoundVariables().size()){ + // This means no variable binding was done, go on with the next constraint info + continue; + } + int i2 = variableCount - newState.getBoundVariables().size(); + + List newIndices = determineIndices(stateTable, i2, newState, k); + int a = newIndices.get(0); + int c = newIndices.get(1); + + if (checkInsertCondition(stateTable.get(i2), newState, reachableBoundVariableSets, a, c, k)) { + updateExtends(newState, currentState, extendOpsByBoundVariables); // preprocess next steps + insert(stateTable,i2, newState, a, c, k); + } + } + } + } + + return stateTable.get(0).get(0); + } + + private List> initializeStateTable(int k, int n) { + List> stateTable = new ArrayList<>(); + // Initialize state table and fill it with null + for (int i = 0; i <= n ; i++) { + stateTable.add(new ArrayList<>()); + } + return stateTable; + } + + private void insert(List> stateTable, int idx, PlanState newState, int a, int c, int k) { + stateTable.get(idx).add(c, newState); + while(stateTable.get(idx).size() > k){ + // Truncate back to size k when grows too big + stateTable.set(idx, stateTable.get(idx).subList(0, k)); + } + } + + private void updateExtends(PlanState newState, PlanState currentState, + Map> extendOpsByBoundVariables) + { + List presentExtends = currentState.getPresentExtends(); + + // Recategorize operations + newState.updateExtendsBasedOnDelta(presentExtends, extendOpsByBoundVariables); + + return; + } + + private boolean checkInsertCondition(List list, PlanState newState, + List> reachableBoundVariableSets, int a, int c, int k) { +// boolean isAmongBestK = (a == (k + 1)) && c < a && reachableBoundVariableSets.contains(newState.getBoundVariables()); + boolean isAmongBestK = a == k && c < a ; + boolean isBetterThanCurrent = a < k && c <= a; + + return isAmongBestK || isBetterThanCurrent; + } + + private List determineIndices(List> stateTable, int i2, PlanState newState, int k) { + int a = k; + int c = 0; + List acIndices = new ArrayList<>(); + for (int j = 0; j < k; j++) { + if (j < stateTable.get(i2).size()) { + PlanState stateInTable = stateTable.get(i2).get(j); + if (newState.getBoundVariables().equals(stateInTable.getBoundVariables())) { + // The new state has the same adornment as the stored one - they are not adornment disjoint + a = j; + } + if (newState.getCost() >= stateInTable.getCost()) { + c = j + 1; + } + } else { + break; + } + } + + acIndices.add(a); + acIndices.add(c); + return acIndices; + } + + private PlanState calculateNextState(PlanState currentState, PConstraintInfo constraintInfo) { + return currentState.cloneWithApplied(constraintInfo); + } + + private List> reachabilityAnalysis(PBody pBody, List constraintInfos) { + // TODO implement reachability analisys, also save/persist the results somewhere + List> reachableBoundVariableSets = new ArrayList<>(); + return reachableBoundVariableSets; + } + + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/PConstraintCategory.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/PConstraintCategory.java new file mode 100644 index 00000000..b98dd12e --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/PConstraintCategory.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Marton Bur, Zoltan Ujhelyi, Akos Horvath, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.planner; + + +/** + * Expresses the state of a PConstraint application + * condition with respect to a given adornment. + * + * @author Marton Bur + * @noreference This enum is not intended to be referenced by clients. + */ +public enum PConstraintCategory { + /* + * During plan creation an operation is considered a past + * operation, if an already bound variable is free in the + * mask of the operation. + * (Mask of the operation: the required binding state of + * the affected variables) + */ + PAST, + /* + * The binding states of the variables in the operation + * mask correspond to the current binding states of the + * variables in the search plan + */ + PRESENT, + /* + * There is at least one bound variable in the mask of + * a future operation that is still free at the current + * state of the plan. Also, a future operation can't be + * PAST. + */ + FUTURE; +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/PConstraintInfo.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/PConstraintInfo.java new file mode 100644 index 00000000..c2c76ef2 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/PConstraintInfo.java @@ -0,0 +1,142 @@ +/** + * Copyright (c) 2010-2015, Marton Bur, Zoltan Ujhelyi, Akos Horvath, Istvan Rath and Danil Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.viatra.runtime.localsearch.planner; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.function.Function; + +import tools.refinery.viatra.runtime.localsearch.planner.cost.IConstraintEvaluationContext; +import tools.refinery.viatra.runtime.matchers.backend.ResultProviderRequestor; +import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; +import tools.refinery.viatra.runtime.matchers.context.IQueryResultProviderAccess; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.analysis.QueryAnalyzer; + +/** + * Wraps a PConstraint together with information required for the planner. Currently contains information about the expected binding state of + * the affected variables also called application condition, and the cost of the enforcement, based on the meta and/or the runtime context. + * + * @author Marton Bur + * @noreference This class is not intended to be referenced by clients. + */ +public class PConstraintInfo implements IConstraintEvaluationContext { + + private PConstraint constraint; + private Set boundMaskVariables; + private Set freeMaskVariables; + private Set sameWithDifferentBindings; + private IQueryRuntimeContext runtimeContext; + private QueryAnalyzer queryAnalyzer; + private IQueryResultProviderAccess resultProviderAccess; + private ResultProviderRequestor resultRequestor; + + private Double cost; + private Function costFunction; + + + /** + * Instantiates the wrapper + * @param constraintfor which the information is added and stored + * @param boundMaskVariables the bound variables in the operation mask + * @param freeMaskVariables the free variables in the operation mask + * @param sameWithDifferentBindings during the planning process, multiple operation adornments are considered for a constraint, so that it + * is represented by multiple plan infos. This parameter contains all plan infos that are for the same + * constraint, but with different adornment + * @param context the query backend context + */ + public PConstraintInfo(PConstraint constraint, Set boundMaskVariables, Set freeMaskVariables, + Set sameWithDifferentBindings, + IQueryBackendContext context, + ResultProviderRequestor resultRequestor, + Function costFunction) { + this.constraint = constraint; + this.costFunction = costFunction; + this.boundMaskVariables = new LinkedHashSet<>(boundMaskVariables); + this.freeMaskVariables = new LinkedHashSet<>(freeMaskVariables); + this.sameWithDifferentBindings = sameWithDifferentBindings; + this.resultRequestor = resultRequestor; + this.runtimeContext = context.getRuntimeContext(); + this.queryAnalyzer = context.getQueryAnalyzer(); + this.resultProviderAccess = context.getResultProviderAccess(); + + this.cost = null; // cost will be computed lazily (esp. important for pattern calls) + } + + @Override + public IQueryRuntimeContext getRuntimeContext() { + return runtimeContext; + } + + @Override + public QueryAnalyzer getQueryAnalyzer() { + return queryAnalyzer; + } + + @Override + public PConstraint getConstraint() { + return constraint; + } + + @Override + public Set getFreeVariables() { + return freeMaskVariables; + } + + @Override + public Set getBoundVariables() { + return boundMaskVariables; + } + + public Set getSameWithDifferentBindings() { + return sameWithDifferentBindings; + } + + public double getCost() { + if (cost == null) { + // Calculate cost of the constraint based on its type + cost = costFunction.apply(this); + } + return cost; + } + + public PConstraintCategory getCategory(PBody pBody, Set boundVariables) { + if (!Collections.disjoint(boundVariables, this.freeMaskVariables)) { + return PConstraintCategory.PAST; + } else if (!boundVariables.containsAll(this.boundMaskVariables)) { + return PConstraintCategory.FUTURE; + } else { + return PConstraintCategory.PRESENT; + } + } + + @Override + public String toString() { + return String.format("%s, bound variables: %s, cost: \"%.2f\"", constraint.toString(), boundMaskVariables.toString(), cost); + } + + /** + * @deprecated use {@link #resultProviderRequestor()} + */ + @Override + @Deprecated + public IQueryResultProviderAccess resultProviderAccess() { + return resultProviderAccess; + } + + @Override + public ResultProviderRequestor resultProviderRequestor() { + return resultRequestor; + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/PConstraintInfoInferrer.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/PConstraintInfoInferrer.java new file mode 100644 index 00000000..96fda930 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/PConstraintInfoInferrer.java @@ -0,0 +1,326 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Grill Balázs, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.planner; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.EStructuralFeature; +import tools.refinery.viatra.runtime.base.api.BaseIndexOptions; +import tools.refinery.viatra.runtime.base.comprehension.EMFModelComprehension; +import tools.refinery.viatra.runtime.emf.types.EStructuralFeatureInstancesKey; +import tools.refinery.viatra.runtime.localsearch.planner.cost.IConstraintEvaluationContext; +import tools.refinery.viatra.runtime.matchers.backend.ResultProviderRequestor; +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.AggregatorConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.ExportedParameter; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.ExpressionEvaluation; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.Inequality; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.PatternMatchCounter; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.TypeFilterConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.AbstractTransitiveClosure; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.ConstantValue; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.PositivePatternCall; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.TypeConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameterDirection; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Sets; + + +/** + * @author Grill Balázs + * @noreference This class is not intended to be referenced by clients. + */ +class PConstraintInfoInferrer { + + private static final Predicate SINGLE_USE_VARIABLE = input -> input != null && input.getReferringConstraints().size() == 1; + + private final boolean useIndex; + private final Function costFunction; + private final EMFModelComprehension modelComprehension; + private final IQueryBackendContext context; + private final ResultProviderRequestor resultRequestor; + + + public PConstraintInfoInferrer(boolean useIndex, + IQueryBackendContext backendContext, + ResultProviderRequestor resultRequestor, + Function costFunction) { + this.useIndex = useIndex; + this.context = backendContext; + this.resultRequestor = resultRequestor; + this.costFunction = costFunction; + this.modelComprehension = new EMFModelComprehension(new BaseIndexOptions()); + } + + + /** + * Create all possible application condition for all constraint + * + * @param constraintSet the set of constraints + * @param runtimeContext the model dependent runtime contest + * @return a collection of the wrapper PConstraintInfo objects with all the allowed application conditions + */ + public List createPConstraintInfos(Set constraintSet) { + List constraintInfos = new ArrayList<>(); + + for (PConstraint pConstraint : constraintSet) { + createPConstraintInfoDispatch(constraintInfos, pConstraint); + } + return constraintInfos; + } + + private void createPConstraintInfoDispatch(List resultList, PConstraint pConstraint){ + if(pConstraint instanceof ExportedParameter){ + createConstraintInfoExportedParameter(resultList, (ExportedParameter) pConstraint); + } else if(pConstraint instanceof TypeConstraint){ + createConstraintInfoTypeConstraint(resultList, (TypeConstraint)pConstraint); + } else if(pConstraint instanceof TypeFilterConstraint){ + createConstraintInfoTypeFilterConstraint(resultList, (TypeFilterConstraint)pConstraint); + } else if(pConstraint instanceof ConstantValue){ + createConstraintInfoConstantValue(resultList, (ConstantValue)pConstraint); + } else if (pConstraint instanceof Inequality){ + createConstraintInfoInequality(resultList, (Inequality) pConstraint); + } else if (pConstraint instanceof ExpressionEvaluation){ + createConstraintInfoExpressionEvaluation(resultList, (ExpressionEvaluation)pConstraint); + } else if (pConstraint instanceof AggregatorConstraint){ + createConstraintInfoAggregatorConstraint(resultList, pConstraint, ((AggregatorConstraint) pConstraint).getResultVariable()); + } else if (pConstraint instanceof PatternMatchCounter){ + createConstraintInfoAggregatorConstraint(resultList, pConstraint, ((PatternMatchCounter) pConstraint).getResultVariable()); + } else if (pConstraint instanceof PositivePatternCall){ + createConstraintInfoPositivePatternCall(resultList, (PositivePatternCall) pConstraint); + } else if (pConstraint instanceof AbstractTransitiveClosure) { + createConstraintInfoBinaryTransitiveClosure(resultList, (AbstractTransitiveClosure) pConstraint); + } else{ + createConstraintInfoGeneric(resultList, pConstraint); + } + } + + private void createConstraintInfoConstantValue(List resultList, + ConstantValue pConstraint) { + // A ConstantValue constraint has a single variable, which is allowed to be unbound + // (extending through ConstantValue is considered a cheap operation) + Set affectedVariables = pConstraint.getAffectedVariables(); + Set> bindings = Sets.powerSet(affectedVariables); + doCreateConstraintInfos(resultList, pConstraint, affectedVariables, bindings); + } + + + private void createConstraintInfoPositivePatternCall(List resultList, + PositivePatternCall pCall) { + // A pattern call can have any of its variables unbound + Set affectedVariables = pCall.getAffectedVariables(); + // IN parameters cannot be unbound and + // OUT parameters cannot be bound + Tuple variables = pCall.getVariablesTuple(); + final Set inVariables = new HashSet<>(); + Set inoutVariables = new HashSet<>(); + List parameters = pCall.getReferredQuery().getParameters(); + for(int i=0;i> bindings = Sets.powerSet(inoutVariables).stream() + .map(input -> Stream.concat(input.stream(), inVariables.stream()).collect(Collectors.toSet())) + .collect(Collectors.toSet()); + + doCreateConstraintInfos(resultList, pCall, affectedVariables, bindings); + } + + private void createConstraintInfoBinaryTransitiveClosure(List resultList, + AbstractTransitiveClosure closure) { + // A pattern call can have any of its variables unbound + + List parameters = closure.getReferredQuery().getParameters(); + Tuple variables = closure.getVariablesTuple(); + + Set> bindings = new HashSet<>(); + PVariable firstVariable = (PVariable) variables.get(0); + PVariable secondVariable = (PVariable) variables.get(1); + // Check is always supported + bindings.add(new HashSet<>(Arrays.asList(firstVariable, secondVariable))); + // If first parameter is not bound mandatorily, it can be left out + if (parameters.get(0).getDirection() != PParameterDirection.IN) { + bindings.add(Collections.singleton(secondVariable)); + } + // If second parameter is not bound mandatorily, it can be left out + if (parameters.get(1).getDirection() != PParameterDirection.IN) { + bindings.add(Collections.singleton(firstVariable)); + } + + doCreateConstraintInfos(resultList, closure, closure.getAffectedVariables(), bindings); + } + + + + private void createConstraintInfoExportedParameter(List resultList, + ExportedParameter parameter) { + // In case of an exported parameter constraint, the parameter must be bound in order to execute + Set affectedVariables = parameter.getAffectedVariables(); + doCreateConstraintInfos(resultList, parameter, affectedVariables, Collections.singleton(affectedVariables)); + } + + private void createConstraintInfoExpressionEvaluation(List resultList, + ExpressionEvaluation expressionEvaluation) { + // An expression evaluation can only have its output variable unbound. All other variables shall be bound + PVariable output = expressionEvaluation.getOutputVariable(); + Set> bindings = new HashSet<>(); + Set affectedVariables = expressionEvaluation.getAffectedVariables(); + // All variables bound -> check + bindings.add(affectedVariables); + // Output variable is not bound -> extend + bindings.add(affectedVariables.stream().filter(var -> !Objects.equals(var, output)).collect(Collectors.toSet())); + doCreateConstraintInfos(resultList, expressionEvaluation, affectedVariables, bindings); + } + + private void createConstraintInfoTypeFilterConstraint(List resultList, + TypeFilterConstraint filter){ + // In case of type filter, all affected variables must be bound in order to execute + Set affectedVariables = filter.getAffectedVariables(); + doCreateConstraintInfos(resultList, filter, affectedVariables, Collections.singleton(affectedVariables)); + } + + private void createConstraintInfoInequality(List resultList, + Inequality inequality){ + // In case of inequality, all affected variables must be bound in order to execute + Set affectedVariables = inequality.getAffectedVariables(); + doCreateConstraintInfos(resultList, inequality, affectedVariables, Collections.singleton(affectedVariables)); + } + + private void createConstraintInfoAggregatorConstraint(List resultList, + PConstraint pConstraint, PVariable resultVariable){ + Set affectedVariables = pConstraint.getAffectedVariables(); + + // The only variables which can be unbound are single-use + Set canBeUnboundVariables = + Stream.concat(Stream.of(resultVariable), affectedVariables.stream().filter(SINGLE_USE_VARIABLE)).collect(Collectors.toSet()); + + Set> bindings = calculatePossibleBindings(canBeUnboundVariables, affectedVariables); + + doCreateConstraintInfos(resultList, pConstraint, affectedVariables, bindings); + } + + /** + * + * @param canBeUnboundVariables Variables which are allowed to be unbound + * @param affectedVariables All affected variables + * @return The set of possible bound variable sets + */ + private Set> calculatePossibleBindings(Set canBeUnboundVariables, Set affectedVariables){ + final Set mustBindVariables = affectedVariables.stream().filter(input -> !canBeUnboundVariables.contains(input)).collect(Collectors.toSet()); + return Sets.powerSet(canBeUnboundVariables).stream() + .map(input -> { + //some variables have to be bound before executing this constraint + Set result= new HashSet<>(input); + result.addAll(mustBindVariables); + return result; + }) + .collect(Collectors.toSet()); + } + + private void createConstraintInfoGeneric(List resultList, PConstraint pConstraint){ + Set affectedVariables = pConstraint.getAffectedVariables(); + + // The only variables which can be unbound are single use variables + Set canBeUnboundVariables = affectedVariables.stream().filter(SINGLE_USE_VARIABLE).collect(Collectors.toSet()); + + Set> bindings = calculatePossibleBindings(canBeUnboundVariables, affectedVariables); + + doCreateConstraintInfos(resultList, pConstraint, affectedVariables, bindings); + } + + private boolean canPerformInverseNavigation(EStructuralFeature feature){ + return ( // Feature has opposite (this only possible for references) + hasEOpposite(feature) + || + (feature instanceof EReference) && ((EReference)feature).isContainment() + || ( // Indexing is enabled, and the feature can be indexed (not a non-well-behaving derived feature). + useIndex && modelComprehension.representable(feature) + )); + } + + private void createConstraintInfoTypeConstraint(List resultList, + TypeConstraint typeConstraint) { + Set affectedVariables = typeConstraint.getAffectedVariables(); + Set> bindings = null; + + IInputKey inputKey = typeConstraint.getSupplierKey(); + if(inputKey.isEnumerable()){ + bindings = Sets.powerSet(affectedVariables); + }else{ + // For not enumerable types, this constraint can only be a check + bindings = Collections.singleton(affectedVariables); + } + + if(inputKey instanceof EStructuralFeatureInstancesKey){ + final EStructuralFeature feature = ((EStructuralFeatureInstancesKey) inputKey).getEmfKey(); + if(!canPerformInverseNavigation(feature)){ + // When inverse navigation is not allowed or not possible, filter out operation masks, where + // the first variable would be free AND the feature is an EReference and has no EOpposite + bindings = excludeUnnavigableOperationMasks(typeConstraint, bindings); + } + } + doCreateConstraintInfos(resultList, typeConstraint, affectedVariables, bindings); + } + + private void doCreateConstraintInfos(List constraintInfos, + PConstraint pConstraint, Set affectedVariables, Iterable> bindings) { + Set sameWithDifferentBindings = new HashSet<>(); + for (Set boundVariables : bindings) { + + PConstraintInfo info = new PConstraintInfo(pConstraint, boundVariables, + affectedVariables.stream().filter(input -> !boundVariables.contains(input)).collect(Collectors.toSet()), + sameWithDifferentBindings, context, resultRequestor, costFunction); + constraintInfos.add(info); + sameWithDifferentBindings.add(info); + } + } + + private Set> excludeUnnavigableOperationMasks(TypeConstraint typeConstraint, Set> bindings) { + PVariable firstVariable = typeConstraint.getVariableInTuple(0); + return bindings.stream().filter( + boundVariablesSet -> (boundVariablesSet.isEmpty() || boundVariablesSet.contains(firstVariable))) + .collect(Collectors.toSet()); + } + + private boolean hasEOpposite(EStructuralFeature feature) { + if(feature instanceof EReference){ + EReference eOpposite = ((EReference) feature).getEOpposite(); + if(eOpposite != null){ + return true; + } + } + return false; + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/PlanState.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/PlanState.java new file mode 100644 index 00000000..e93b07bc --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/PlanState.java @@ -0,0 +1,283 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Marton Bur, Zoltan Ujhelyi, Akos Horvath, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.planner; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import tools.refinery.viatra.runtime.localsearch.planner.util.OperationCostComparator; +import tools.refinery.viatra.runtime.matchers.algorithms.OrderedIterableMerge; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; + +/** + * This class represents the state of the plan during planning. + * + *

A PlanState represents a sequence of operations (operationsList) and caches the computed cost + * for this operation sequence. The list and the cost are initialized in the constructor. + * However, #categorizeChecks() also updates the operations list (by suffixing checks) + * + * @author Marton Bur + * @noreference This class is not intended to be referenced by clients. + */ +public class PlanState { + + private final PBody pBody; + private final List operationsList; + private final Set boundVariables; + private final Collection deltaVariables; /* bound since ancestor plan */ + private final Set enforcedConstraints; + + private double cummulativeProduct; + private double cost; + + private static Comparator infoComparator = new OperationCostComparator(); + + /* + * For a short explanation of past, present and future operations, + * see class + */ + private List presentExtends; + + /** + * Creates an initial state + */ + public PlanState(PBody pBody, Set boundVariables) { + + this(pBody, new ArrayList<>(), boundVariables, boundVariables /* also the delta */, + 0.0 /* initial cost */, 1.0 /*initial branch count */); + } + + public PlanState cloneWithApplied(PConstraintInfo op) { + // Create operation list based on the current state + ArrayList newOperationsList = + // pre-reserve excess capacity for later addition of CHECK ops + new ArrayList<>(pBody.getConstraints().size()); + newOperationsList.addAll(this.getOperations()); + newOperationsList.add(op); + + // Bind the variables of the op + Collection deltaVariables = op.getFreeVariables(); + Set allBoundVariables = + // pre-reserve exact capacity as variables are known + // (will not be affected by adding CHECK ops later) + new HashSet<>(this.getBoundVariables().size() + deltaVariables.size()); + allBoundVariables.addAll(this.getBoundVariables()); + allBoundVariables.addAll(deltaVariables); + + PlanState newState = new PlanState(getAssociatedPBody(), newOperationsList, allBoundVariables, deltaVariables, + cost, cummulativeProduct); + newState.accountNewOperation(op); + return newState; + } + + private PlanState(PBody pBody, List operationsList, + Set boundVariables, Collection deltaVariables, + double cost, double cummulativeProduct) + { + this.pBody = pBody; + this.operationsList = operationsList; + this.boundVariables = boundVariables; + this.enforcedConstraints = new HashSet<>(); + this.deltaVariables = deltaVariables; + this.cost = cost; + this.cummulativeProduct = cummulativeProduct; + } + + // NOT included for EXTEND: bind all variables of op + private void accountNewOperation(PConstraintInfo constraintInfo) { + this.enforcedConstraints.add(constraintInfo.getConstraint()); + accountCost(constraintInfo); + } + + private void accountCost(PConstraintInfo constraintInfo) { + double constraintCost = constraintInfo.getCost(); + double branchFactor = constraintCost; + if (constraintCost > 0){ + cost += cummulativeProduct * constraintCost; + cummulativeProduct *= branchFactor; + } + } + + + public Set getEnforcedConstraints() { + return enforcedConstraints; + } + + /** + * Re-categorizes given extend operations into already applied or no longer applicable ones (discarded), + * immediately applicable ones (saved as presently viable extends), + * and not yet applicable ones (discarded). + * + * @param allPotentialExtendInfos all other extends that may be applicable + * to this plan state now or in the future; + * MUST consist of "extend" constraint applications only (at least one free variable) + */ + public void updateExtends(Iterable allPotentialExtendInfos) { + presentExtends = new ArrayList<>(); + + + // categorize future/present extend constraint infos + for (PConstraintInfo op : allPotentialExtendInfos) { + updateExtendInternal(op); + } + } + + /** + * Re-categorizes given extend operations into already applied or no longer applicable ones (discarded), + * immediately applicable ones (saved as presently viable extends), + * and not yet applicable ones (discarded). + * + * @param extendOpsByBoundVariables all EXTEND operations indexed by affected bound variables + * MUST consist of "extend" constraint applications only (at least one free variable) + */ + public void updateExtendsBasedOnDelta( + Iterable previousPresentExtends, + Map> extendOpsByBoundVariables) + { + presentExtends = new ArrayList<>(); + if (operationsList.isEmpty()) + throw new IllegalStateException("Not applicable as starting step"); + + for (PConstraintInfo extend: previousPresentExtends) { + updateExtendInternal(extend); + } + + Set affectedExtends = new HashSet<>(); + for (PVariable variable : deltaVariables) { + // only those check ops may become applicable that have an affected variable in the delta + Collection extendsForVariable = extendOpsByBoundVariables.get(variable); + if (null != extendsForVariable) { + affectedExtends.addAll(extendsForVariable); + } + } + for (PConstraintInfo extend: affectedExtends) { + updateExtendInternal(extend); + } + } + + private void updateExtendInternal(PConstraintInfo op) { + if(!enforcedConstraints.contains(op.getConstraint())) { + categorizeExtend(op); + } + } + + /** + * Check operations that newly became applicable (see {@link #getDeltaVariables()}) + * are appended to operations lists. + * + *

Will never discover degenerate checks (of PConstraints with zero variables), + * so must not use on initial state. + * + * @param allPotentialCheckInfos all CHECK operations + * MUST consist of "check" constraint applications only (no free variables) + * and must be iterable in decreasing order of cost + * + * + */ + public void applyChecks(List allPotentialCheckInfos) { + applyChecksInternal(allPotentialCheckInfos); + } + + /** + * Immediately applicable checks are appended to operations lists. + * + * @param checkOpsByVariables all CHECK operations indexed by affected variables + * MUST consist of "check" constraint applications only (no free variables) + * and each bucket must be iterable in decreasing order of cost + */ + public void applyChecksBasedOnDelta(Map> checkOpsByVariables) { + if (operationsList.isEmpty()) + throw new IllegalStateException("Not applicable as starting step"); + + Iterable affectedChecks = Collections.emptyList(); + + for (PVariable variable : deltaVariables) { + // only those check ops may become applicable that have an affected variable in the delta + List checksForVariable = checkOpsByVariables.get(variable); + if (null != checksForVariable) { + affectedChecks = OrderedIterableMerge.mergeUniques(affectedChecks, checksForVariable, infoComparator); + } + } + + // checks retain their order, no re-sorting needed + applyChecksInternal(affectedChecks); + } + + private void applyChecksInternal(Iterable checks) { + for (PConstraintInfo checkInfo : checks) { + if (this.boundVariables.containsAll(checkInfo.getBoundVariables()) && + !enforcedConstraints.contains(checkInfo.getConstraint())) + { + operationsList.add(checkInfo); + accountNewOperation(checkInfo); + } + } + } + + + private void categorizeExtend(PConstraintInfo constraintInfo) { + PConstraintCategory category = constraintInfo.getCategory(pBody, boundVariables); + if (category == PConstraintCategory.PRESENT) { + presentExtends.add(constraintInfo); + } else { + // do not categorize past/future operations + } + } + + + public PBody getAssociatedPBody() { + return pBody; + } + + public List getOperations() { + return operationsList; + } + + public Set getBoundVariables() { + return boundVariables; + } + + /** + * @return the derived cost of the plan contained in the state + */ + public double getCost() { + return cost; + } + + + /** + * @return cumulative branching factor + * @since 2.1 + */ + public double getCummulativeProduct() { + return cummulativeProduct; + } + + public List getPresentExtends() { + return presentExtends; + } + + /** + * Contains only those variables that are added by the newest extend + * (or the initially bound ones if no extend yet) + */ + public Collection getDeltaVariables() { + return deltaVariables; + } + + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/compiler/AbstractOperationCompiler.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/compiler/AbstractOperationCompiler.java new file mode 100644 index 00000000..73312dc9 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/compiler/AbstractOperationCompiler.java @@ -0,0 +1,430 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.planner.compiler; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import tools.refinery.viatra.runtime.localsearch.matcher.CallWithAdornment; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; +import tools.refinery.viatra.runtime.localsearch.operations.check.AggregatorCheck; +import tools.refinery.viatra.runtime.localsearch.operations.check.BinaryTransitiveClosureCheck; +import tools.refinery.viatra.runtime.localsearch.operations.check.CheckConstant; +import tools.refinery.viatra.runtime.localsearch.operations.check.CheckPositivePatternCall; +import tools.refinery.viatra.runtime.localsearch.operations.check.CountCheck; +import tools.refinery.viatra.runtime.localsearch.operations.check.ExpressionCheck; +import tools.refinery.viatra.runtime.localsearch.operations.check.ExpressionEvalCheck; +import tools.refinery.viatra.runtime.localsearch.operations.check.InequalityCheck; +import tools.refinery.viatra.runtime.localsearch.operations.check.NACOperation; +import tools.refinery.viatra.runtime.localsearch.operations.extend.AggregatorExtend; +import tools.refinery.viatra.runtime.localsearch.operations.extend.CountOperation; +import tools.refinery.viatra.runtime.localsearch.operations.extend.ExpressionEval; +import tools.refinery.viatra.runtime.localsearch.operations.extend.ExtendBinaryTransitiveClosure; +import tools.refinery.viatra.runtime.localsearch.operations.extend.ExtendConstant; +import tools.refinery.viatra.runtime.localsearch.operations.extend.ExtendPositivePatternCall; +import tools.refinery.viatra.runtime.localsearch.operations.util.CallInformation; +import tools.refinery.viatra.runtime.localsearch.planner.util.CompilerHelper; +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.planning.QueryProcessingException; +import tools.refinery.viatra.runtime.matchers.planning.SubPlan; +import tools.refinery.viatra.runtime.matchers.planning.operations.PApply; +import tools.refinery.viatra.runtime.matchers.planning.operations.POperation; +import tools.refinery.viatra.runtime.matchers.planning.operations.PProject; +import tools.refinery.viatra.runtime.matchers.planning.operations.PStart; +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.AggregatorConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.ExportedParameter; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.ExpressionEvaluation; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.Inequality; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.NegativePatternCall; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.PatternMatchCounter; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.TypeFilterConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.BinaryReflexiveTransitiveClosure; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.BinaryTransitiveClosure; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.ConstantValue; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.PositivePatternCall; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.TypeConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter; + +/** + * @author Zoltan Ujhelyi + * @since 1.7 + * + */ +public abstract class AbstractOperationCompiler implements IOperationCompiler { + + protected static final String UNSUPPORTED_TYPE_MESSAGE = "Unsupported type: "; + + protected abstract void createExtend(TypeConstraint typeConstraint, Map variableMapping); + + /** + * @throws ViatraQueryRuntimeException + */ + protected abstract void createCheck(TypeConstraint typeConstraint, Map variableMapping); + + /** + * @throws ViatraQueryRuntimeException + */ + protected abstract void createCheck(TypeFilterConstraint typeConstraint, Map variableMapping); + + /** + * @since 2.0 + * @throws ViatraQueryRuntimeException + */ + protected abstract void createUnaryTypeCheck(IInputKey type, int position); + + protected List operations; + protected Set dependencies = new HashSet<>(); + protected Map> variableBindings; + private Map variableMappings; + protected final IQueryRuntimeContext runtimeContext; + + public AbstractOperationCompiler(IQueryRuntimeContext runtimeContext) { + this.runtimeContext = runtimeContext; + } + + /** + * Compiles a plan of POperations to a list of type List<ISearchOperation> + * + * @param plan + * @param boundParameters + * @return an ordered list of POperations that make up the compiled search plan + * @throws ViatraQueryRuntimeException + */ + @Override + public List compile(SubPlan plan, Set boundParameters) { + + variableMappings = CompilerHelper.createVariableMapping(plan); + variableBindings = CompilerHelper.cacheVariableBindings(plan, variableMappings, boundParameters); + + operations = new ArrayList<>(); + + List operationList = CompilerHelper.createOperationsList(plan); + for (POperation pOperation : operationList) { + compile(pOperation, variableMappings); + } + + return operations; + } + + private void compile(POperation pOperation, Map variableMapping) { + + if (pOperation instanceof PApply) { + PApply pApply = (PApply) pOperation; + PConstraint pConstraint = pApply.getPConstraint(); + + if (isCheck(pConstraint, variableMapping)) { + // check + createCheckDispatcher(pConstraint, variableMapping); + } else { + // extend + createExtendDispatcher(pConstraint, variableMapping); + } + + } else if (pOperation instanceof PStart) { + // nop + } else if (pOperation instanceof PProject) { + // nop + } else { + throw new QueryProcessingException("PStart, PApply or PProject was expected, received: " + pOperation.getClass(), null,"Unexpected POperation type", null); + } + + } + + private void createCheckDispatcher(PConstraint pConstraint, Map variableMapping) { + + + // DeferredPConstraint subclasses + + // Equalities are normalized + + if (pConstraint instanceof Inequality) { + createCheck((Inequality) pConstraint, variableMapping); + } else if (pConstraint instanceof PositivePatternCall){ + createCheck((PositivePatternCall) pConstraint, variableMapping); + } else if (pConstraint instanceof NegativePatternCall) { + createCheck((NegativePatternCall) pConstraint,variableMapping); + } else if (pConstraint instanceof AggregatorConstraint) { + createCheck((AggregatorConstraint) pConstraint, variableMapping); + } else if (pConstraint instanceof PatternMatchCounter) { + createCheck((PatternMatchCounter) pConstraint, variableMapping); + } else if (pConstraint instanceof ExpressionEvaluation) { + createCheck((ExpressionEvaluation) pConstraint, variableMapping); + } else if (pConstraint instanceof TypeFilterConstraint) { + createCheck((TypeFilterConstraint) pConstraint,variableMapping); + } else if (pConstraint instanceof ExportedParameter) { + // Nothing to do here + } else + + // EnumerablePConstraint subclasses + + if (pConstraint instanceof BinaryTransitiveClosure) { + createCheck((BinaryTransitiveClosure) pConstraint, variableMapping); + } else if (pConstraint instanceof BinaryReflexiveTransitiveClosure) { + createCheck((BinaryReflexiveTransitiveClosure)pConstraint, variableMapping); + } else if (pConstraint instanceof ConstantValue) { + createCheck((ConstantValue) pConstraint, variableMapping); + } else if (pConstraint instanceof TypeConstraint) { + createCheck((TypeConstraint) pConstraint,variableMapping); + } else { + String msg = "Unsupported Check constraint: "+pConstraint.toString(); + throw new QueryProcessingException(msg, null, msg, null); + } + + } + + protected void createExtendDispatcher(PConstraint pConstraint, Map variableMapping) { + + // DeferredPConstraint subclasses + + // Equalities are normalized + if (pConstraint instanceof PositivePatternCall) { + createExtend((PositivePatternCall)pConstraint, variableMapping); + } else if (pConstraint instanceof AggregatorConstraint) { + createExtend((AggregatorConstraint) pConstraint, variableMapping); + } else if (pConstraint instanceof PatternMatchCounter) { + createExtend((PatternMatchCounter) pConstraint, variableMapping); + } else if (pConstraint instanceof ExpressionEvaluation) { + createExtend((ExpressionEvaluation) pConstraint, variableMapping); + } else if (pConstraint instanceof ExportedParameter) { + // ExportedParameters are compiled to NOP + } else + + // EnumerablePConstraint subclasses + + if (pConstraint instanceof ConstantValue) { + createExtend((ConstantValue) pConstraint, variableMapping); + } else if (pConstraint instanceof TypeConstraint) { + createExtend((TypeConstraint) pConstraint, variableMapping); + } else if (pConstraint instanceof BinaryTransitiveClosure) { + createExtend((BinaryTransitiveClosure)pConstraint, variableMapping); + } else if (pConstraint instanceof BinaryReflexiveTransitiveClosure) { + createExtend((BinaryReflexiveTransitiveClosure)pConstraint, variableMapping); + } else { + String msg = "Unsupported Extend constraint: "+pConstraint.toString(); + throw new QueryProcessingException(msg, null, msg, null); + } + } + + private boolean isCheck(PConstraint pConstraint, final Map variableMapping) { + if (pConstraint instanceof NegativePatternCall){ + return true; + }else if (pConstraint instanceof PositivePatternCall){ + // Positive pattern call is check if all non-single used variables are bound + List callVariables = pConstraint.getAffectedVariables().stream() + .filter(input -> input.getReferringConstraints().size() > 1) + .map(variableMapping::get) + .collect(Collectors.toList()); + return variableBindings.get(pConstraint).containsAll(callVariables); + }else if (pConstraint instanceof AggregatorConstraint){ + PVariable outputvar = ((AggregatorConstraint) pConstraint).getResultVariable(); + return variableBindings.get(pConstraint).contains(variableMapping.get(outputvar)); + }else if (pConstraint instanceof PatternMatchCounter){ + PVariable outputvar = ((PatternMatchCounter) pConstraint).getResultVariable(); + return variableBindings.get(pConstraint).contains(variableMapping.get(outputvar)); + }else if (pConstraint instanceof ExpressionEvaluation){ + PVariable outputvar = ((ExpressionEvaluation) pConstraint).getOutputVariable(); + return outputvar == null || variableBindings.get(pConstraint).contains(variableMapping.get(outputvar)); + } else { + // In other cases, all variables shall be bound to be a check + Set affectedVariables = pConstraint.getAffectedVariables(); + Set varIndices = new HashSet<>(); + for (PVariable variable : affectedVariables) { + varIndices.add(variableMapping.get(variable)); + } + return variableBindings.get(pConstraint).containsAll(varIndices); + } + } + + @Override + public Set getDependencies() { + return dependencies; + } + + /** + * @return the cached variable bindings for the previously created plan + */ + @Override + public Map getVariableMappings() { + return variableMappings; + } + + protected void createCheck(PatternMatchCounter counter, Map variableMapping) { + CallInformation information = CallInformation.create(counter, variableMapping, variableBindings.get(counter)); + operations.add(new CountCheck(information, variableMapping.get(counter.getResultVariable()))); + dependencies.add(information.getCallWithAdornment()); + } + + protected void createCheck(PositivePatternCall pCall, Map variableMapping) { + CallInformation information = CallInformation.create(pCall, variableMapping, variableBindings.get(pCall)); + operations.add(new CheckPositivePatternCall(information)); + dependencies.add(information.getCallWithAdornment()); + } + + protected void createCheck(ConstantValue constant, Map variableMapping) { + int position = variableMapping.get(constant.getVariablesTuple().get(0)); + operations.add(new CheckConstant(position, constant.getSupplierKey())); + } + + protected void createCheck(BinaryTransitiveClosure binaryTransitiveClosure, Map variableMapping) { + int sourcePosition = variableMapping.get(binaryTransitiveClosure.getVariablesTuple().get(0)); + int targetPosition = variableMapping.get(binaryTransitiveClosure.getVariablesTuple().get(1)); + + //The second parameter is NOT bound during execution! + CallInformation information = CallInformation.create(binaryTransitiveClosure, variableMapping, Stream.of(sourcePosition).collect(Collectors.toSet())); + operations.add(new BinaryTransitiveClosureCheck(information, sourcePosition, targetPosition, false)); + dependencies.add(information.getCallWithAdornment()); + } + + /** + * @since 2.0 + */ + protected void createCheck(BinaryReflexiveTransitiveClosure binaryTransitiveClosure, Map variableMapping) { + int sourcePosition = variableMapping.get(binaryTransitiveClosure.getVariablesTuple().get(0)); + int targetPosition = variableMapping.get(binaryTransitiveClosure.getVariablesTuple().get(1)); + + //The second parameter is NOT bound during execution! + CallInformation information = CallInformation.create(binaryTransitiveClosure, variableMapping, Stream.of(sourcePosition).collect(Collectors.toSet())); + createUnaryTypeCheck(binaryTransitiveClosure.getUniverseType(), sourcePosition); + operations.add(new BinaryTransitiveClosureCheck(information, sourcePosition, targetPosition, true)); + dependencies.add(information.getCallWithAdornment()); + } + + protected void createCheck(ExpressionEvaluation expressionEvaluation, Map variableMapping) { + // Fill unbound variables with null; simply copy all variables. Unbound variables will be null anyway + Iterable inputParameterNames = expressionEvaluation.getEvaluator().getInputParameterNames(); + Map nameMap = new HashMap<>(); + + for (String pVariableName : inputParameterNames) { + PVariable pVariable = expressionEvaluation.getPSystem().getVariableByNameChecked(pVariableName); + nameMap.put(pVariableName, variableMapping.get(pVariable)); + } + + // output variable can be null; if null it is an ExpressionCheck + if(expressionEvaluation.getOutputVariable() == null){ + operations.add(new ExpressionCheck(expressionEvaluation.getEvaluator(), nameMap)); + } else { + operations.add(new ExpressionEvalCheck(expressionEvaluation.getEvaluator(), nameMap, expressionEvaluation.isUnwinding(), variableMapping.get(expressionEvaluation.getOutputVariable()))); + } + } + + protected void createCheck(AggregatorConstraint aggregator, Map variableMapping) { + CallInformation information = CallInformation.create(aggregator, variableMapping, variableBindings.get(aggregator)); + operations.add(new AggregatorCheck(information, aggregator, variableMapping.get(aggregator.getResultVariable()))); + dependencies.add(information.getCallWithAdornment()); + } + + protected void createCheck(NegativePatternCall negativePatternCall, Map variableMapping) { + CallInformation information = CallInformation.create(negativePatternCall, variableMapping, variableBindings.get(negativePatternCall)); + operations.add(new NACOperation(information)); + dependencies.add(information.getCallWithAdornment()); + } + + protected void createCheck(Inequality inequality, Map variableMapping) { + operations.add(new InequalityCheck(variableMapping.get(inequality.getWho()), variableMapping.get(inequality.getWithWhom()))); + } + + protected void createExtend(PositivePatternCall pCall, Map variableMapping) { + CallInformation information = CallInformation.create(pCall, variableMapping, variableBindings.get(pCall)); + operations.add(new ExtendPositivePatternCall(information)); + dependencies.add(information.getCallWithAdornment()); + } + + protected void createExtend(BinaryTransitiveClosure binaryTransitiveClosure, Map variableMapping) { + int sourcePosition = variableMapping.get(binaryTransitiveClosure.getVariablesTuple().get(0)); + int targetPosition = variableMapping.get(binaryTransitiveClosure.getVariablesTuple().get(1)); + + boolean sourceBound = variableBindings.get(binaryTransitiveClosure).contains(sourcePosition); + boolean targetBound = variableBindings.get(binaryTransitiveClosure).contains(targetPosition); + + CallInformation information = CallInformation.create(binaryTransitiveClosure, variableMapping, variableBindings.get(binaryTransitiveClosure)); + + if (sourceBound && !targetBound) { + operations.add(new ExtendBinaryTransitiveClosure.Forward(information, sourcePosition, targetPosition, false)); + dependencies.add(information.getCallWithAdornment()); + } else if (!sourceBound && targetBound) { + operations.add(new ExtendBinaryTransitiveClosure.Backward(information, sourcePosition, targetPosition, false)); + dependencies.add(information.getCallWithAdornment()); + } else { + String msg = "Binary transitive closure not supported with two unbound parameters"; + throw new QueryProcessingException(msg, null, msg, binaryTransitiveClosure.getPSystem().getPattern()); + } + } + + /** + * @since 2.0 + */ + protected void createExtend(BinaryReflexiveTransitiveClosure binaryTransitiveClosure, Map variableMapping) { + int sourcePosition = variableMapping.get(binaryTransitiveClosure.getVariablesTuple().get(0)); + int targetPosition = variableMapping.get(binaryTransitiveClosure.getVariablesTuple().get(1)); + + boolean sourceBound = variableBindings.get(binaryTransitiveClosure).contains(sourcePosition); + boolean targetBound = variableBindings.get(binaryTransitiveClosure).contains(targetPosition); + + CallInformation information = CallInformation.create(binaryTransitiveClosure, variableMapping, variableBindings.get(binaryTransitiveClosure)); + + if (sourceBound && !targetBound) { + createUnaryTypeCheck(binaryTransitiveClosure.getUniverseType(), sourcePosition); + operations.add(new ExtendBinaryTransitiveClosure.Forward(information, sourcePosition, targetPosition, true)); + dependencies.add(information.getCallWithAdornment()); + } else if (!sourceBound && targetBound) { + createUnaryTypeCheck(binaryTransitiveClosure.getUniverseType(), targetPosition); + operations.add(new ExtendBinaryTransitiveClosure.Backward(information, sourcePosition, targetPosition, true)); + dependencies.add(information.getCallWithAdornment()); + } else { + String msg = "Binary reflective transitive closure not supported with two unbound parameters"; + throw new QueryProcessingException(msg, null, msg, binaryTransitiveClosure.getPSystem().getPattern()); + } + } + + protected void createExtend(ConstantValue constant, Map variableMapping) { + int position = variableMapping.get(constant.getVariablesTuple().get(0)); + operations.add(new ExtendConstant(position, constant.getSupplierKey())); + } + + protected void createExtend(ExpressionEvaluation expressionEvaluation, Map variableMapping) { + // Fill unbound variables with null; simply copy all variables. Unbound variables will be null anyway + Iterable inputParameterNames = expressionEvaluation.getEvaluator().getInputParameterNames(); + Map nameMap = new HashMap<>(); + + for (String pVariableName : inputParameterNames) { + PVariable pVariable = expressionEvaluation.getPSystem().getVariableByNameChecked(pVariableName); + nameMap.put(pVariableName, variableMapping.get(pVariable)); + } + + // output variable can be null; if null it is an ExpressionCheck + if(expressionEvaluation.getOutputVariable() == null){ + operations.add(new ExpressionCheck(expressionEvaluation.getEvaluator(), nameMap)); + } else { + operations.add(new ExpressionEval(expressionEvaluation.getEvaluator(), nameMap, expressionEvaluation.isUnwinding(), variableMapping.get(expressionEvaluation.getOutputVariable()))); + } + } + + protected void createExtend(AggregatorConstraint aggregator, Map variableMapping) { + CallInformation information = CallInformation.create(aggregator, variableMapping, variableBindings.get(aggregator)); + operations.add(new AggregatorExtend(information, aggregator, variableMapping.get(aggregator.getResultVariable()))); + dependencies.add(information.getCallWithAdornment()); + } + + protected void createExtend(PatternMatchCounter patternMatchCounter, Map variableMapping) { + CallInformation information = CallInformation.create(patternMatchCounter, variableMapping, variableBindings.get(patternMatchCounter)); + operations.add(new CountOperation(information, variableMapping.get(patternMatchCounter.getResultVariable()))); + dependencies.add(information.getCallWithAdornment()); + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/compiler/EMFOperationCompiler.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/compiler/EMFOperationCompiler.java new file mode 100644 index 00000000..7fd86f3c --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/compiler/EMFOperationCompiler.java @@ -0,0 +1,198 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.planner.compiler; + +import java.util.Map; + +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.EStructuralFeature; +import tools.refinery.viatra.runtime.emf.EMFQueryRuntimeContext; +import tools.refinery.viatra.runtime.emf.types.EClassTransitiveInstancesKey; +import tools.refinery.viatra.runtime.emf.types.EClassUnscopedTransitiveInstancesKey; +import tools.refinery.viatra.runtime.emf.types.EDataTypeInSlotsKey; +import tools.refinery.viatra.runtime.emf.types.EStructuralFeatureInstancesKey; +import tools.refinery.viatra.runtime.localsearch.operations.check.InstanceOfClassCheck; +import tools.refinery.viatra.runtime.localsearch.operations.check.InstanceOfDataTypeCheck; +import tools.refinery.viatra.runtime.localsearch.operations.check.InstanceOfJavaClassCheck; +import tools.refinery.viatra.runtime.localsearch.operations.check.StructuralFeatureCheck; +import tools.refinery.viatra.runtime.localsearch.operations.check.nobase.ScopeCheck; +import tools.refinery.viatra.runtime.localsearch.operations.extend.ExtendToEStructuralFeatureSource; +import tools.refinery.viatra.runtime.localsearch.operations.extend.ExtendToEStructuralFeatureTarget; +import tools.refinery.viatra.runtime.localsearch.operations.extend.IterateOverContainers; +import tools.refinery.viatra.runtime.localsearch.operations.extend.IterateOverEClassInstances; +import tools.refinery.viatra.runtime.localsearch.operations.extend.IterateOverEDatatypeInstances; +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.context.common.JavaTransitiveInstancesKey; +import tools.refinery.viatra.runtime.matchers.planning.QueryProcessingException; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.TypeFilterConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.TypeConstraint; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; + +/** + * Operation compiler implementation that uses EMF-specific operations. + * + * @author Zoltan Ujhelyi + * @since 1.7 + * + */ +public class EMFOperationCompiler extends AbstractOperationCompiler { + + private boolean baseIndexAvailable; + + private final EMFQueryRuntimeContext runtimeContext; + + public EMFOperationCompiler(IQueryRuntimeContext runtimeContext) { + this(runtimeContext, false); + } + + public EMFOperationCompiler(IQueryRuntimeContext runtimeContext, boolean baseIndexAvailable) { + super(runtimeContext); + this.runtimeContext = (EMFQueryRuntimeContext) runtimeContext; + this.baseIndexAvailable = baseIndexAvailable; + } + + @Override + protected void createCheck(TypeFilterConstraint typeConstraint, Map variableMapping) { + final IInputKey inputKey = typeConstraint.getInputKey(); + if (inputKey instanceof JavaTransitiveInstancesKey) { + doCreateInstanceofJavaTypeCheck((JavaTransitiveInstancesKey) inputKey, variableMapping.get(typeConstraint.getVariablesTuple().get(0))); + } else if (inputKey instanceof EDataTypeInSlotsKey) { // TODO probably only occurs as TypeConstraint + doCreateInstanceofDatatypeCheck((EDataTypeInSlotsKey) inputKey, variableMapping.get(typeConstraint.getVariablesTuple().get(0))); + } else if (inputKey instanceof EClassUnscopedTransitiveInstancesKey) { + doCreateInstanceofUnscopedClassCheck((EClassUnscopedTransitiveInstancesKey) inputKey, variableMapping.get(typeConstraint.getVariablesTuple().get(0))); + } else { + String msg = UNSUPPORTED_TYPE_MESSAGE + inputKey; + throw new QueryProcessingException(msg, null, msg, null); + } + } + + @Override + protected void createCheck(TypeConstraint typeConstraint, Map variableMapping) { + final IInputKey inputKey = typeConstraint.getSupplierKey(); + if (inputKey instanceof EClassTransitiveInstancesKey) { + doCreateInstanceofClassCheck((EClassTransitiveInstancesKey)inputKey, variableMapping.get(typeConstraint.getVariablesTuple().get(0))); + } else if (inputKey instanceof EStructuralFeatureInstancesKey) { + int sourcePosition = variableMapping.get(typeConstraint.getVariablesTuple().get(0)); + int targetPosition = variableMapping.get(typeConstraint.getVariablesTuple().get(1)); + operations.add(new StructuralFeatureCheck(sourcePosition, targetPosition, + ((EStructuralFeatureInstancesKey) inputKey).getEmfKey())); + } else if (inputKey instanceof EDataTypeInSlotsKey) { + doCreateInstanceofDatatypeCheck((EDataTypeInSlotsKey) inputKey, variableMapping.get(typeConstraint.getVariablesTuple().get(0))); + } else { + String msg = UNSUPPORTED_TYPE_MESSAGE + inputKey; + throw new QueryProcessingException(msg, null, msg, null); + } + } + + @Override + protected void createUnaryTypeCheck(IInputKey inputKey, int position) { + if (inputKey instanceof EClassTransitiveInstancesKey) { + doCreateInstanceofClassCheck((EClassTransitiveInstancesKey)inputKey, position); + } else if (inputKey instanceof EClassUnscopedTransitiveInstancesKey) { + doCreateInstanceofUnscopedClassCheck((EClassUnscopedTransitiveInstancesKey)inputKey, position); + } else if (inputKey instanceof EDataTypeInSlotsKey) { + doCreateInstanceofDatatypeCheck((EDataTypeInSlotsKey) inputKey, position); + } else if (inputKey instanceof JavaTransitiveInstancesKey) { + doCreateInstanceofJavaTypeCheck((JavaTransitiveInstancesKey) inputKey, position); + } else { + String msg = UNSUPPORTED_TYPE_MESSAGE + inputKey; + throw new QueryProcessingException(msg, null, msg, null); + } + } + + private void doCreateInstanceofClassCheck(EClassTransitiveInstancesKey inputKey, int position) { + operations.add(new InstanceOfClassCheck(position, inputKey.getEmfKey())); + operations.add(new ScopeCheck(position, runtimeContext.getEmfScope())); + } + + private void doCreateInstanceofUnscopedClassCheck(EClassUnscopedTransitiveInstancesKey inputKey, int position) { + operations.add(new InstanceOfClassCheck(position, inputKey.getEmfKey())); + } + + private void doCreateInstanceofDatatypeCheck(EDataTypeInSlotsKey inputKey, int position) { + operations.add(new InstanceOfDataTypeCheck(position, inputKey.getEmfKey())); + } + private void doCreateInstanceofJavaTypeCheck(JavaTransitiveInstancesKey inputKey, int position) { + operations.add(new InstanceOfJavaClassCheck(position, inputKey.getInstanceClass())); + } + + @Override + public void createExtend(TypeConstraint typeConstraint, Map variableMapping) { + final IInputKey inputKey = typeConstraint.getSupplierKey(); + if (inputKey instanceof EDataTypeInSlotsKey) { + if(baseIndexAvailable){ + operations.add(new IterateOverEDatatypeInstances(variableMapping.get(typeConstraint.getVariableInTuple(0)), ((EDataTypeInSlotsKey) inputKey).getEmfKey())); + } else { + int position = variableMapping.get(typeConstraint.getVariableInTuple(0)); + operations + .add(new tools.refinery.viatra.runtime.localsearch.operations.extend.nobase.IterateOverEDatatypeInstances(position, + ((EDataTypeInSlotsKey) inputKey).getEmfKey(), runtimeContext.getEmfScope())); + operations.add(new ScopeCheck(position, runtimeContext.getEmfScope())); + } + } else if (inputKey instanceof EClassTransitiveInstancesKey) { + if(baseIndexAvailable){ + operations.add(new IterateOverEClassInstances(variableMapping.get(typeConstraint.getVariableInTuple(0)), + ((EClassTransitiveInstancesKey) inputKey).getEmfKey())); + } else { + int position = variableMapping.get(typeConstraint.getVariableInTuple(0)); + operations + .add(new tools.refinery.viatra.runtime.localsearch.operations.extend.nobase.IterateOverEClassInstances( + position, + ((EClassTransitiveInstancesKey) inputKey).getEmfKey(), runtimeContext.getEmfScope())); + operations.add(new ScopeCheck(position, runtimeContext.getEmfScope())); + } + } else if (inputKey instanceof EStructuralFeatureInstancesKey) { + final EStructuralFeature feature = ((EStructuralFeatureInstancesKey) inputKey).getEmfKey(); + + int sourcePosition = variableMapping.get(typeConstraint.getVariablesTuple().get(0)); + int targetPosition = variableMapping.get(typeConstraint.getVariablesTuple().get(1)); + + boolean fromBound = variableBindings.get(typeConstraint).contains(sourcePosition); + boolean toBound = variableBindings.get(typeConstraint).contains(targetPosition); + + if (fromBound && !toBound) { + operations.add(new ExtendToEStructuralFeatureTarget(sourcePosition, targetPosition, feature)); + operations.add(new ScopeCheck(targetPosition, runtimeContext.getEmfScope())); + } else if(!fromBound && toBound){ + if (feature instanceof EReference && ((EReference)feature).isContainment()) { + // The iterate is also used to traverse a single container (third parameter) + operations.add(new IterateOverContainers(sourcePosition, targetPosition, false)); + operations.add(new ScopeCheck(sourcePosition, runtimeContext.getEmfScope())); + } else if(baseIndexAvailable){ + TupleMask mask = TupleMask.fromSelectedIndices(variableMapping.size(), new int[] {targetPosition}); + operations.add(new ExtendToEStructuralFeatureSource(sourcePosition, targetPosition, feature, mask)); + } else { + operations.add(new tools.refinery.viatra.runtime.localsearch.operations.extend.nobase.ExtendToEStructuralFeatureSource( + sourcePosition, targetPosition, feature)); + operations.add(new ScopeCheck(sourcePosition, runtimeContext.getEmfScope())); + } + } else { + // TODO Elaborate solution based on the navigability of edges + // As of now a static solution is implemented + if (baseIndexAvailable) { + operations.add(new IterateOverEClassInstances(sourcePosition, feature.getEContainingClass())); + operations.add(new ExtendToEStructuralFeatureTarget(sourcePosition, targetPosition, feature)); + } else { + operations + .add(new tools.refinery.viatra.runtime.localsearch.operations.extend.nobase.IterateOverEClassInstances( + sourcePosition, feature.getEContainingClass(), runtimeContext.getEmfScope())); + operations.add(new ScopeCheck(sourcePosition, runtimeContext.getEmfScope())); + operations.add(new ExtendToEStructuralFeatureTarget(sourcePosition, targetPosition, feature)); + operations.add(new ScopeCheck(targetPosition, runtimeContext.getEmfScope())); + } + } + + } else { + throw new IllegalArgumentException(UNSUPPORTED_TYPE_MESSAGE + inputKey); + } + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/compiler/GenericOperationCompiler.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/compiler/GenericOperationCompiler.java new file mode 100644 index 00000000..606048b7 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/compiler/GenericOperationCompiler.java @@ -0,0 +1,102 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.planner.compiler; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import tools.refinery.viatra.runtime.localsearch.operations.generic.GenericTypeCheck; +import tools.refinery.viatra.runtime.localsearch.operations.generic.GenericTypeExtend; +import tools.refinery.viatra.runtime.localsearch.operations.generic.GenericTypeExtendSingleValue; +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.TypeFilterConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.TypeConstraint; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; + +/** + * @author Zoltan Ujhelyi + * @since 1.7 + * + */ +public class GenericOperationCompiler extends AbstractOperationCompiler { + + public GenericOperationCompiler(IQueryRuntimeContext runtimeContext) { + super(runtimeContext); + } + + @Override + protected void createCheck(TypeFilterConstraint typeConstraint, Map variableMapping) { + IInputKey inputKey = typeConstraint.getInputKey(); + Tuple tuple = typeConstraint.getVariablesTuple(); + int[] positions = new int[tuple.getSize()]; + for (int i = 0; i < tuple.getSize(); i++) { + PVariable variable = (PVariable) tuple.get(i); + positions[i] = variableMapping.get(variable); + } + operations.add(new GenericTypeCheck(inputKey, positions, TupleMask.fromSelectedIndices(variableMapping.size(), positions))); + + } + + @Override + protected void createCheck(TypeConstraint typeConstraint, Map variableMapping) { + IInputKey inputKey = typeConstraint.getSupplierKey(); + Tuple tuple = typeConstraint.getVariablesTuple(); + int[] positions = new int[tuple.getSize()]; + for (int i = 0; i < tuple.getSize(); i++) { + PVariable variable = (PVariable) tuple.get(i); + positions[i] = variableMapping.get(variable); + } + operations.add(new GenericTypeCheck(inputKey, positions, TupleMask.fromSelectedIndices(variableMapping.size(), positions))); + } + + @Override + protected void createUnaryTypeCheck(IInputKey inputKey, int position) { + int[] positions = new int[] {position}; + operations.add(new GenericTypeCheck(inputKey, positions, TupleMask.fromSelectedIndices(1, positions))); + } + + @Override + protected void createExtend(TypeConstraint typeConstraint, Map variableMapping) { + IInputKey inputKey = typeConstraint.getSupplierKey(); + Tuple tuple = typeConstraint.getVariablesTuple(); + + int[] positions = new int[tuple.getSize()]; + List boundVariableIndices = new ArrayList<>(); + List boundVariables = new ArrayList<>(); + Set unboundVariables = new HashSet<>(); + for (int i = 0; i < tuple.getSize(); i++) { + PVariable variable = (PVariable) tuple.get(i); + Integer position = variableMapping.get(variable); + positions[i] = position; + if (variableBindings.get(typeConstraint).contains(position)) { + boundVariableIndices.add(i); + boundVariables.add(position); + } else { + unboundVariables.add(position); + } + } + TupleMask indexerMask = TupleMask.fromSelectedIndices(inputKey.getArity(), boundVariableIndices); + TupleMask callMask = TupleMask.fromSelectedIndices(variableMapping.size(), boundVariables); + if (unboundVariables.size() == 1) { + operations.add(new GenericTypeExtendSingleValue(inputKey, positions, callMask, indexerMask, unboundVariables.iterator().next())); + } else { + operations.add(new GenericTypeExtend(inputKey, positions, callMask, indexerMask, unboundVariables)); + } + + } + + + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/compiler/IOperationCompiler.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/compiler/IOperationCompiler.java new file mode 100644 index 00000000..625a7eb2 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/compiler/IOperationCompiler.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.planner.compiler; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import tools.refinery.viatra.runtime.localsearch.matcher.CallWithAdornment; +import tools.refinery.viatra.runtime.localsearch.matcher.MatcherReference; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; +import tools.refinery.viatra.runtime.matchers.planning.SubPlan; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter; + +/** + * An operation compiler is responsible for creating executable search plans from the subplan structure. + * + * @author Zoltan Ujhelyi + * @since 1.7 + * + */ +public interface IOperationCompiler { + + /** + * Compiles a plan of POperations to a list of type List<ISearchOperation> + * + * @param plan + * @param boundParameters + * @return an ordered list of POperations that make up the compiled search plan + * @throws ViatraQueryRuntimeException + */ + List compile(SubPlan plan, Set boundParameters); + + /** + * Replaces previous method returning {@link MatcherReference} + * @since 2.1 + */ + Set getDependencies(); + + /** + * @return the cached variable bindings for the previously created plan + */ + Map getVariableMappings(); + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/IConstraintEvaluationContext.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/IConstraintEvaluationContext.java new file mode 100644 index 00000000..9b44612b --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/IConstraintEvaluationContext.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Grill Balázs, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.planner.cost; + +import tools.refinery.viatra.runtime.matchers.backend.ResultProviderRequestor; +import tools.refinery.viatra.runtime.matchers.context.IQueryResultProviderAccess; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; +import java.util.Collection; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.analysis.QueryAnalyzer; + +/** + * This interface denotes the evaluation context of a constraint, intended for cost estimation. Provides access to information + * on which the cost function can base its calculation. + * + * @author Grill Balázs + * @since 1.4 + * @noimplement + */ +public interface IConstraintEvaluationContext { + + /** + * Get the constraint to be evaluated + */ + public PConstraint getConstraint(); + + /** + * Unbound variables at the time of evaluating the constraint + */ + public Collection getFreeVariables(); + + /** + * Bound variables at the time of evaluating the constraint + */ + public Collection getBoundVariables(); + + public IQueryRuntimeContext getRuntimeContext(); + + /** + * @since 1.5 + */ + public QueryAnalyzer getQueryAnalyzer(); + + /** + * @deprecated use {@link #resultProviderRequestor()} + * @since 1.5 + */ + @Deprecated + public IQueryResultProviderAccess resultProviderAccess(); + + /** + * @since 2.1 + */ + public ResultProviderRequestor resultProviderRequestor(); + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/ICostFunction.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/ICostFunction.java new file mode 100644 index 00000000..4d9d0708 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/ICostFunction.java @@ -0,0 +1,22 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Grill Balázs, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.planner.cost; + +/** + * Common interface for cost function implementation + * + * @author Grill Balázs + * @since 1.4 + * + */ +public interface ICostFunction{ + + public double apply(IConstraintEvaluationContext input); + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/impl/HybridMatcherConstraintCostFunction.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/impl/HybridMatcherConstraintCostFunction.java new file mode 100644 index 00000000..df9292f0 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/impl/HybridMatcherConstraintCostFunction.java @@ -0,0 +1,91 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Grill Balázs, IncQuery Labs Ltd + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.planner.cost.impl; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import tools.refinery.viatra.runtime.localsearch.planner.cost.IConstraintEvaluationContext; +import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider; +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.ConstantValue; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.PositivePatternCall; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; + +/** + * This cost function is intended to be used on hybrid configuration, with the strict restriction than any + * non-flattened positive pattern call is executed with Rete engine. This implementation provides the exact number + * of matches by invoking the result provider for the called pattern. + * + * @deprecated {@link StatisticsBasedConstraintCostFunction} should use {@link IQueryResultProvider#estimateCardinality(tools.refinery.viatra.runtime.matchers.tuple.TupleMask, org.eclipse.viatra.query.runtime.matchers.util.Accuracy)} + */ +@Deprecated +public class HybridMatcherConstraintCostFunction extends IndexerBasedConstraintCostFunction { + + @Override + protected double _calculateCost(PositivePatternCall patternCall, IConstraintEvaluationContext context) { + // Determine local constant constraints which is used to filter results + Tuple variables = patternCall.getVariablesTuple(); + Set variablesSet = variables.getDistinctElements(); + final Map constantMap = new HashMap<>(); + for (PConstraint _constraint : patternCall.getPSystem().getConstraints()) { + if (_constraint instanceof ConstantValue){ + ConstantValue constraint = (ConstantValue) _constraint; + PVariable variable = (PVariable) constraint.getVariablesTuple().get(0); + if (variablesSet.contains(variable) && context.getBoundVariables().contains(variable)) { + constantMap.put(variable, constraint.getSupplierKey()); + } + } + } + + // Determine filter + Object[] filter = new Object[variables.getSize()]; + for(int i=0; i < variables.getSize(); i++){ + filter[i] = constantMap.get(variables.get(i)); + } + + // aggregate keys are the bound and not filtered variables + // These will be fixed in runtime, but unknown at planning time + // This is represented by indices to ease working with result tuples + final Map variableIndices = variables.invertIndex(); + List aggregateKeys = context.getBoundVariables().stream() + .filter(input -> !constantMap.containsKey(input)) + .map(variableIndices::get) + .collect(Collectors.toList()); + + IQueryResultProvider resultProvider = context.resultProviderRequestor().requestResultProvider(patternCall, null); + Map aggregatedCounts = new HashMap<>(); + + // Iterate over all matches and count together matches that has equal values on + // aggregateKeys positions. The cost of the pattern call is considered to be the + // Maximum of these counted values + + int result = 0; + // NOTE: a stream is not an iterable (cannot be iterated more than once), so to use it in a for-loop + // it has to be wrapped; in the following line a lambda is used to implement Iterable#iterator() + for (Tuple match : (Iterable) () -> resultProvider.getAllMatches(filter).iterator()) { + Tuple extracted = Tuples.flatTupleOf(aggregateKeys.stream().map(match::get).toArray()); + int count = (aggregatedCounts.containsKey(extracted)) + ? aggregatedCounts.get(extracted) + 1 + : 1; + aggregatedCounts.put(extracted, count); + if (result < count) { + result = count; + } + } + + return result; + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/impl/IndexerBasedConstraintCostFunction.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/impl/IndexerBasedConstraintCostFunction.java new file mode 100644 index 00000000..9e2c8680 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/impl/IndexerBasedConstraintCostFunction.java @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2010-2016, Grill Balázs, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.viatra.runtime.localsearch.planner.cost.impl; + +import java.util.Optional; + +import tools.refinery.viatra.runtime.localsearch.planner.cost.IConstraintEvaluationContext; +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.Accuracy; + +/** + * Cost function which calculates cost based on the cardinality of items in the runtime model, provided by the base indexer + * + * @author Grill Balázs + * @since 1.4 + */ +public class IndexerBasedConstraintCostFunction extends StatisticsBasedConstraintCostFunction { + + + + + /** + * + */ + public IndexerBasedConstraintCostFunction() { + super(); + } + + /** + * @param inverseNavigationPenalty + * @since 2.1 + */ + public IndexerBasedConstraintCostFunction(double inverseNavigationPenalty) { + super(inverseNavigationPenalty); + } + + @Override + public Optional projectionSize(IConstraintEvaluationContext input, IInputKey supplierKey, TupleMask groupMask, Accuracy requiredAccuracy) { + return input.getRuntimeContext().estimateCardinality(supplierKey, groupMask, requiredAccuracy); + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/impl/StatisticsBasedConstraintCostFunction.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/impl/StatisticsBasedConstraintCostFunction.java new file mode 100644 index 00000000..873be31d --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/impl/StatisticsBasedConstraintCostFunction.java @@ -0,0 +1,413 @@ +/** + * Copyright (c) 2010-2016, Grill Balázs, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.viatra.runtime.localsearch.planner.cost.impl; + +import static tools.refinery.viatra.runtime.matchers.planning.helpers.StatisticsHelper.min; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import tools.refinery.viatra.runtime.localsearch.matcher.integration.AbstractLocalSearchResultProvider; +import tools.refinery.viatra.runtime.localsearch.planner.cost.IConstraintEvaluationContext; +import tools.refinery.viatra.runtime.localsearch.planner.cost.ICostFunction; +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; +import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider; +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.planning.helpers.FunctionalDependencyHelper; +import tools.refinery.viatra.runtime.matchers.psystem.IQueryReference; +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.analysis.QueryAnalyzer; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.AggregatorConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.ExportedParameter; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.ExpressionEvaluation; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.Inequality; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.NegativePatternCall; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.PatternMatchCounter; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.TypeFilterConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.BinaryReflexiveTransitiveClosure; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.BinaryTransitiveClosure; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.ConstantValue; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.PositivePatternCall; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.TypeConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.Accuracy; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; + +/** + * Cost function which calculates cost based on the cardinality of items in the runtime model + * + *

To provide custom statistics, override + * {@link #projectionSize(IConstraintEvaluationContext, IInputKey, TupleMask, Accuracy)} + * and {@link #bucketSize(IQueryReference, IConstraintEvaluationContext, TupleMask)}. + * + * @author Grill Balázs + * @since 1.4 + */ +public abstract class StatisticsBasedConstraintCostFunction implements ICostFunction { + protected static final double MAX_COST = 250.0; + + protected static final double DEFAULT_COST = StatisticsBasedConstraintCostFunction.MAX_COST - 100.0; + + /** + * @since 2.1 + */ + public static final double INVERSE_NAVIGATION_PENALTY_DEFAULT = 0.10; + /** + * @since 2.1 + */ + public static final double INVERSE_NAVIGATION_PENALTY_GENERIC = 0.01; + /** + * @since 2.7 + */ + public static final double EVAL_UNWIND_EXTENSION_FACTOR = 3.0; + + private final double inverseNavigationPenalty; + + + /** + * @since 2.1 + */ + public StatisticsBasedConstraintCostFunction(double inverseNavigationPenalty) { + super(); + this.inverseNavigationPenalty = inverseNavigationPenalty; + } + public StatisticsBasedConstraintCostFunction() { + this(INVERSE_NAVIGATION_PENALTY_DEFAULT); + } + + /** + * @deprecated call and implement {@link #projectionSize(IConstraintEvaluationContext, IInputKey, TupleMask, Accuracy)} instead + */ + @Deprecated + public long countTuples(final IConstraintEvaluationContext input, final IInputKey supplierKey) { + return projectionSize(input, supplierKey, TupleMask.identity(supplierKey.getArity()), Accuracy.EXACT_COUNT).orElse(-1L); + } + + /** + * Override this to provide custom statistics on edge/node counts. + * New implementors shall implement this instead of {@link #countTuples(IConstraintEvaluationContext, IInputKey)} + * @since 2.1 + */ + public Optional projectionSize(final IConstraintEvaluationContext input, final IInputKey supplierKey, + final TupleMask groupMask, Accuracy requiredAccuracy) { + long legacyCount = countTuples(input, supplierKey); + return legacyCount < 0 ? Optional.empty() : Optional.of(legacyCount); + } + + /** + * Override this to provide custom estimates for match set sizes of called patterns. + * @since 2.1 + */ + public Optional bucketSize(final IQueryReference patternCall, + final IConstraintEvaluationContext input, TupleMask projMask) { + IQueryResultProvider resultProvider = input.resultProviderRequestor().requestResultProvider(patternCall, null); + // TODO hack: use LS cost instead of true bucket size estimate + if (resultProvider instanceof AbstractLocalSearchResultProvider) { + double estimatedCost = ((AbstractLocalSearchResultProvider) resultProvider).estimateCost(projMask); + return Optional.of(estimatedCost); + } else { + return resultProvider.estimateAverageBucketSize(projMask, Accuracy.APPROXIMATION); + } + } + + + + @Override + public double apply(final IConstraintEvaluationContext input) { + return this.calculateCost(input.getConstraint(), input); + } + + protected double _calculateCost(final ConstantValue constant, final IConstraintEvaluationContext input) { + return 0.0f; + } + + protected double _calculateCost(final TypeConstraint constraint, final IConstraintEvaluationContext input) { + final Collection freeMaskVariables = input.getFreeVariables(); + final Collection boundMaskVariables = input.getBoundVariables(); + IInputKey supplierKey = constraint.getSupplierKey(); + long arity = supplierKey.getArity(); + + if ((arity == 1)) { + // unary constraint + return calculateUnaryConstraintCost(constraint, input); + } else if ((arity == 2)) { + // binary constraint + PVariable srcVariable = ((PVariable) constraint.getVariablesTuple().get(0)); + PVariable dstVariable = ((PVariable) constraint.getVariablesTuple().get(1)); + boolean isInverse = false; + // Check if inverse navigation is needed along the edge + if ((freeMaskVariables.contains(srcVariable) && boundMaskVariables.contains(dstVariable))) { + isInverse = true; + } + double binaryExtendCost = calculateBinaryCost(supplierKey, srcVariable, dstVariable, isInverse, input); + // Make inverse navigation slightly more expensive than forward navigation + // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=501078 + return (isInverse) ? binaryExtendCost + inverseNavigationPenalty : binaryExtendCost; + } else { + // n-ary constraint + throw new UnsupportedOperationException("Cost calculation for arity " + arity + " is not implemented yet"); + } + } + + + /** + * @deprecated use/implement {@link #calculateBinaryCost(IInputKey, PVariable, PVariable, boolean, IConstraintEvaluationContext)} instead + */ + @Deprecated + protected double calculateBinaryExtendCost(final IInputKey supplierKey, final PVariable srcVariable, + final PVariable dstVariable, final boolean isInverse, long edgeCount /* TODO remove */, + final IConstraintEvaluationContext input) { + throw new UnsupportedOperationException(); + } + + /** + * @since 2.1 + */ + protected double calculateBinaryCost(final IInputKey supplierKey, final PVariable srcVariable, + final PVariable dstVariable, final boolean isInverse, + final IConstraintEvaluationContext input) { + final Collection freeMaskVariables = input.getFreeVariables(); + final PConstraint constraint = input.getConstraint(); + +// IQueryMetaContext metaContext = input.getRuntimeContext().getMetaContext(); +// Collection implications = metaContext.getImplications(supplierKey); + + Optional edgeUpper = projectionSize(input, supplierKey, TupleMask.identity(2), Accuracy.BEST_UPPER_BOUND); + Optional srcUpper = projectionSize(input, supplierKey, TupleMask.selectSingle(0, 2), Accuracy.BEST_UPPER_BOUND); + Optional dstUpper = projectionSize(input, supplierKey, TupleMask.selectSingle(1, 2), Accuracy.BEST_UPPER_BOUND); + + if (freeMaskVariables.contains(srcVariable) && freeMaskVariables.contains(dstVariable)) { + Double branchCount = edgeUpper.map(Long::doubleValue).orElse( + srcUpper.map(Long::doubleValue).orElse(DEFAULT_COST) + * + dstUpper.map(Long::doubleValue).orElse(DEFAULT_COST) + ); + return branchCount; + + } else { + + Optional srcLower = projectionSize(input, supplierKey, TupleMask.selectSingle(0, 2), Accuracy.APPROXIMATION); + Optional dstLower = projectionSize(input, supplierKey, TupleMask.selectSingle(1, 2), Accuracy.APPROXIMATION); + + List> nodeLower = Arrays.asList(srcLower, dstLower); + List> nodeUpper = Arrays.asList(srcUpper, dstUpper); + + int from = isInverse ? 1 : 0; + int to = isInverse ? 0 : 1; + + Optional costEstimate = Optional.empty(); + + if (!freeMaskVariables.contains(srcVariable) && !freeMaskVariables.contains(dstVariable)) { + // both variables bound, this is a simple check + costEstimate = min(costEstimate, 0.9); + } // TODO use bucket size estimation in the runtime context + costEstimate = min(costEstimate, + edgeUpper.flatMap(edges -> + nodeLower.get(from).map(fromNodes -> + // amortize edges over start nodes + (fromNodes == 0) ? 0.0 : (((double) edges) / fromNodes) + ))); + if (navigatesThroughFunctionalDependencyInverse(input, constraint)) { + costEstimate = min(costEstimate, nodeUpper.get(to).flatMap(toNodes -> + nodeLower.get(from).map(fromNodes -> + // due to a reverse functional dependency, the destination count is an upper bound for the edge count + (fromNodes == 0) ? 0.0 : ((double) toNodes) / fromNodes + ))); + } + if (! edgeUpper.isPresent()) { + costEstimate = min(costEstimate, nodeUpper.get(to).flatMap(toNodes -> + nodeLower.get(from).map(fromNodes -> + // If count is 0, no such element exists in the model, so there will be no branching + // TODO rethink, why dstNodeCount / srcNodeCount instead of dstNodeCount? + // The universally valid bound would be something like sparseEdgeEstimate = dstNodeCount + 1.0 + // If we assume sparseness, we can reduce it by a SPARSENESS_FACTOR (e.g. 0.1). + // Alternatively, discount dstNodeCount * srcNodeCount on a SPARSENESS_EXPONENT (e.g 0.75) and then amortize over srcNodeCount. + fromNodes != 0 ? Math.max(1.0, ((double) toNodes) / fromNodes) : 1.0 + ))); + } + if (navigatesThroughFunctionalDependency(input, constraint)) { + // At most one destination value + costEstimate = min(costEstimate, 1.0); + } + + return costEstimate.orElse(DEFAULT_COST); + + } + } + + /** + * @since 1.7 + */ + protected boolean navigatesThroughFunctionalDependency(final IConstraintEvaluationContext input, + final PConstraint constraint) { + return navigatesThroughFunctionalDependency(input, constraint, input.getBoundVariables(), input.getFreeVariables()); + } + /** + * @since 2.1 + */ + protected boolean navigatesThroughFunctionalDependencyInverse(final IConstraintEvaluationContext input, + final PConstraint constraint) { + return navigatesThroughFunctionalDependency(input, constraint, input.getFreeVariables(), input.getBoundVariables()); + } + /** + * @since 2.1 + */ + protected boolean navigatesThroughFunctionalDependency(final IConstraintEvaluationContext input, + final PConstraint constraint, Collection determining, Collection determined) { + final QueryAnalyzer queryAnalyzer = input.getQueryAnalyzer(); + final Map, Set> functionalDependencies = queryAnalyzer + .getFunctionalDependencies(Collections.singleton(constraint), false); + final Set impliedVariables = FunctionalDependencyHelper.closureOf(determining, + functionalDependencies); + return ((impliedVariables != null) && impliedVariables.containsAll(determined)); + } + + protected double calculateUnaryConstraintCost(final TypeConstraint constraint, + final IConstraintEvaluationContext input) { + PVariable variable = (PVariable) constraint.getVariablesTuple().get(0); + if (input.getBoundVariables().contains(variable)) { + return 0.9; + } else { + return projectionSize(input, constraint.getSupplierKey(), TupleMask.identity(1), Accuracy.APPROXIMATION) + .map(count -> 1.0 + count).orElse(DEFAULT_COST); + } + } + + protected double _calculateCost(final ExportedParameter exportedParam, final IConstraintEvaluationContext input) { + return 0.0; + } + + protected double _calculateCost(final TypeFilterConstraint exportedParam, + final IConstraintEvaluationContext input) { + return 0.0; + } + + protected double _calculateCost(final PositivePatternCall patternCall, final IConstraintEvaluationContext input) { + final List boundPositions = new ArrayList<>(); + final List parameters = patternCall.getReferredQuery().getParameters(); + for (int i = 0; (i < parameters.size()); i++) { + final PVariable variable = patternCall.getVariableInTuple(i); + if (input.getBoundVariables().contains(variable)) boundPositions.add(i); + } + TupleMask projMask = TupleMask.fromSelectedIndices(parameters.size(), boundPositions); + + return bucketSize(patternCall, input, projMask).orElse(DEFAULT_COST); + } + + + /** + * @since 1.7 + */ + protected double _calculateCost(final ExpressionEvaluation evaluation, final IConstraintEvaluationContext input) { + // Even if there are multiple results here, if all output variable is bound eval unwind will not result in + // multiple branches in search graph + final double multiplier = evaluation.isUnwinding() && !input.getFreeVariables().isEmpty() + ? EVAL_UNWIND_EXTENSION_FACTOR + : 1.0; + return _calculateCost((PConstraint) evaluation, input) * multiplier; + } + + /** + * @since 1.7 + */ + protected double _calculateCost(final Inequality inequality, final IConstraintEvaluationContext input) { + return _calculateCost((PConstraint)inequality, input); + } + + /** + * @since 1.7 + */ + protected double _calculateCost(final AggregatorConstraint aggregator, final IConstraintEvaluationContext input) { + return _calculateCost((PConstraint)aggregator, input); + } + + /** + * @since 1.7 + */ + protected double _calculateCost(final NegativePatternCall call, final IConstraintEvaluationContext input) { + return _calculateCost((PConstraint)call, input); + } + + /** + * @since 1.7 + */ + protected double _calculateCost(final PatternMatchCounter counter, final IConstraintEvaluationContext input) { + return _calculateCost((PConstraint)counter, input); + } + + /** + * @since 1.7 + */ + protected double _calculateCost(final BinaryTransitiveClosure closure, final IConstraintEvaluationContext input) { + // if (input.getFreeVariables().size() == 1) return 3.0; + return StatisticsBasedConstraintCostFunction.DEFAULT_COST; + } + + /** + * @since 2.0 + */ + protected double _calculateCost(final BinaryReflexiveTransitiveClosure closure, final IConstraintEvaluationContext input) { + // if (input.getFreeVariables().size() == 1) return 3.0; + return StatisticsBasedConstraintCostFunction.DEFAULT_COST; + } + + /** + * Default cost calculation strategy + */ + protected double _calculateCost(final PConstraint constraint, final IConstraintEvaluationContext input) { + if (input.getFreeVariables().isEmpty()) { + return 1.0; + } else { + return StatisticsBasedConstraintCostFunction.DEFAULT_COST; + } + } + + /** + * @throws ViatraQueryRuntimeException + */ + public double calculateCost(final PConstraint constraint, final IConstraintEvaluationContext input) { + Preconditions.checkArgument(constraint != null, "Set constraint value correctly"); + if (constraint instanceof ExportedParameter) { + return _calculateCost((ExportedParameter) constraint, input); + } else if (constraint instanceof TypeFilterConstraint) { + return _calculateCost((TypeFilterConstraint) constraint, input); + } else if (constraint instanceof ConstantValue) { + return _calculateCost((ConstantValue) constraint, input); + } else if (constraint instanceof PositivePatternCall) { + return _calculateCost((PositivePatternCall) constraint, input); + } else if (constraint instanceof TypeConstraint) { + return _calculateCost((TypeConstraint) constraint, input); + } else if (constraint instanceof ExpressionEvaluation) { + return _calculateCost((ExpressionEvaluation) constraint, input); + } else if (constraint instanceof Inequality) { + return _calculateCost((Inequality) constraint, input); + } else if (constraint instanceof AggregatorConstraint) { + return _calculateCost((AggregatorConstraint) constraint, input); + } else if (constraint instanceof NegativePatternCall) { + return _calculateCost((NegativePatternCall) constraint, input); + } else if (constraint instanceof PatternMatchCounter) { + return _calculateCost((PatternMatchCounter) constraint, input); + } else if (constraint instanceof BinaryTransitiveClosure) { + return _calculateCost((BinaryTransitiveClosure) constraint, input); + } else if (constraint instanceof BinaryReflexiveTransitiveClosure) { + return _calculateCost((BinaryReflexiveTransitiveClosure) constraint, input); + } else { + // Default cost calculation + return _calculateCost(constraint, input); + } + } +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/impl/VariableBindingBasedCostFunction.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/impl/VariableBindingBasedCostFunction.java new file mode 100644 index 00000000..a517af25 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/cost/impl/VariableBindingBasedCostFunction.java @@ -0,0 +1,95 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Marton Bur, Balazs Grill, Akos Horvath, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.planner.cost.impl; + +import java.util.Set; + +import tools.refinery.viatra.runtime.localsearch.planner.cost.IConstraintEvaluationContext; +import tools.refinery.viatra.runtime.localsearch.planner.cost.ICostFunction; +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.AggregatorConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.ExportedParameter; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.NegativePatternCall; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.BinaryTransitiveClosure; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.ConstantValue; + +/** + * This class can be used to calculate cost of application of a constraint with a given adornment. + * + * For now the logic is based on the following principles: + * + *

  • The transitive closures, NACs and count finds are the most expensive operations + * + *
  • The number of free variables increase the cost + * + *
  • If all the variables of a constraint are free, then its cost equals to twice the number of its parameter + * variables. This solves the problem of unnecessary iteration over instances at the beginning of a plan (thus causing + * very long run times when executing the plan) by applying constraints based on structural features as soon as + * possible. + * + *
    + * + * @author Marton Bur + * @since 1.4 + * + */ +public class VariableBindingBasedCostFunction implements ICostFunction { + + // Static cost definitions + private static int MAX = 1000; + private static int exportedParameterCost = MAX - 20; + private static int binaryTransitiveClosureCost = MAX - 50; + private static int nacCost = MAX - 100; + private static int aggregatorCost = MAX - 200; + private static int constantCost = 0; + + @Override + public double apply(IConstraintEvaluationContext input) { + PConstraint constraint = input.getConstraint(); + Set affectedVariables = constraint.getAffectedVariables(); + + int cost = 0; + + // For constants the cost is determined to be 0.0 + // The following constraints should be checks: + // * Binary transitive closure + // * NAC + // * count + // * exported parameter - only a metadata + if (constraint instanceof ConstantValue) { + cost = constantCost; + } else if (constraint instanceof BinaryTransitiveClosure) { + cost = binaryTransitiveClosureCost; + } else if (constraint instanceof NegativePatternCall) { + cost = nacCost; + } else if (constraint instanceof AggregatorConstraint) { + cost = aggregatorCost; + } else if (constraint instanceof ExportedParameter) { + cost = exportedParameterCost; + } else { + // In case of other constraints count the number of unbound variables + for (PVariable pVariable : affectedVariables) { + if (input.getFreeVariables().contains(pVariable)) { + // For each free variable ('without-value-variable') increase cost + cost += 1; + } + } + if (cost == affectedVariables.size()) { + // If all the variables are free, double the cost. + // This ensures that iteration costs more + cost *= 2; + } + + } + + return Float.valueOf(cost); + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/util/CompilerHelper.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/util/CompilerHelper.java new file mode 100644 index 00000000..9b4e9ea5 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/util/CompilerHelper.java @@ -0,0 +1,209 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Marton Bur, Daniel Segesdi, Akos Horvath, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.planner.util; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.planning.SubPlan; +import tools.refinery.viatra.runtime.matchers.planning.operations.PApply; +import tools.refinery.viatra.runtime.matchers.planning.operations.POperation; +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.ExportedParameter; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter; + +/** + * + * Helper methods for compiling SubPlans + * + * @author Marton Bur + * + */ +public class CompilerHelper { + + private CompilerHelper() {/*Utility class constructor*/} + + private static boolean isUserSpecified(PVariable var) { + return !var.isVirtual() && !var.getName().startsWith("_"); + } + + public static Map createVariableMapping(SubPlan plan) { + Map variableMapping = new HashMap<>(); + + int variableNumber = 0; + + // Important note: this list might contain duplications when parameters are made equal inside the pattern + // This is the expected and normal behavior + List symbolicParameterVariables = plan.getBody().getSymbolicParameterVariables(); + for (PVariable pVariable : symbolicParameterVariables) { + if (!variableMapping.containsKey(pVariable)) { + variableMapping.put(pVariable, variableNumber++); + } + } + + List allVariables = new ArrayList<>(plan.getBody().getUniqueVariables()); + Collections.sort(allVariables, (left, right) -> { + boolean leftUserSpecified = isUserSpecified(left); + boolean rightUserSpecified = isUserSpecified(right); + if (leftUserSpecified && !rightUserSpecified) { + return -1; + } else if (!leftUserSpecified && rightUserSpecified) { + return +1; + } else { + return left.getName().compareTo(right.getName()); + } + }); + for (PVariable pVariable : allVariables) { + if (!variableMapping.containsKey(pVariable)) { + variableMapping.put(pVariable, variableNumber++); + } + } + + return variableMapping; + } + + public static Map> cacheVariableBindings(SubPlan plan, + Map variableMappings, Set adornment) { + + Set externallyBoundVariables = getVariableIndicesForParameters(plan, variableMappings, + adornment); + + Map> variableBindings = new HashMap<>(); + + List allPlansInHierarchy = getAllParentPlans(plan); + for (SubPlan subPlan : allPlansInHierarchy) { + POperation operation = subPlan.getOperation(); + + if (operation instanceof PApply) { + PConstraint pConstraint = ((PApply) operation).getPConstraint(); + Set boundVariableIndices = getParametersBoundByParentPlan(variableMappings, subPlan); + boundVariableIndices.addAll(externallyBoundVariables); + + variableBindings.put(pConstraint, boundVariableIndices); + } + } + return variableBindings; + } + + /** + * Returns the list of variable indexes that are bound by the parent plan. + */ + private static Set getParametersBoundByParentPlan(Map variableMappings, + SubPlan subPlan) { + if (!subPlan.getParentPlans().isEmpty()) { + SubPlan parentPlan = subPlan.getParentPlans().get(0); + Set enforcedConstraints = parentPlan.getAllEnforcedConstraints(); + Set affectedVariables = getAffectedVariables(enforcedConstraints); + return getVariableIndices(variableMappings, affectedVariables); + } + return Collections.emptySet(); + } + + /** + * @param plan + * @return all the ancestor plans including the given plan + */ + private static List getAllParentPlans(SubPlan plan) { + SubPlan currentPlan = plan; + List allPlans = new ArrayList<>(); + allPlans.add(plan); + while (!currentPlan.getParentPlans().isEmpty()) { + // In the local search it is assumed that only a single parent exists + currentPlan = currentPlan.getParentPlans().get(0); + allPlans.add(currentPlan); + } + + return allPlans; + } + + /** + * @param variableMappings + * the mapping between variables and their indices + * @param variables + * variables to get the indices for + * @return the set of variable indices for the given variables + */ + private static Set getVariableIndices(Map variableMappings, + Iterable variables) { + Set variableIndices = new HashSet<>(); + for (PVariable pVariable : variables) { + variableIndices.add(variableMappings.get(pVariable)); + } + return variableIndices; + } + + /** + * Returns all affected variables of the given PConstraints. + */ + private static Set getAffectedVariables(Set pConstraints) { + Set allDeducedVariables = new HashSet<>(); + for (PConstraint pConstraint : pConstraints) { + allDeducedVariables.addAll(pConstraint.getAffectedVariables()); + } + return allDeducedVariables; + } + + /** + * Transforms the index of a parameter into the index of a variable of the normalized body. + * + * @param plan + * the SubPlan containing the original body and its parameters + * @param variableMappings + * the mapping of PVariables to their indices + * @param parameters + * a set of parameters + * @return the index of the variable corresponding to the parameter at the given index + */ + private static Set getVariableIndicesForParameters(SubPlan plan, + Map variableMappings, Set parameters) { + Map parameterMapping = new HashMap<>(); + for (ExportedParameter constraint : plan.getBody().getSymbolicParameters()) { + parameterMapping.put(constraint.getPatternParameter(), constraint.getParameterVariable()); + } + + Set variableIndices = new HashSet<>(); + for (PParameter parameter : parameters) { + PVariable parameterVariable = parameterMapping.get(parameter); + if (parameterVariable == null) { + // XXX In case of older (pre-1.4) VIATRA versions, PParameters were not stable, see bug 498348 + parameterVariable = plan.getBody().getVariableByNameChecked(parameter.getName()); + } + Integer variableIndex = variableMappings.get(parameterVariable); + variableIndices.add(variableIndex); + } + return variableIndices; + } + + /** + * Extracts the operations from a SubPlan into a list of POperations in the order of execution + * + * @param plan + * the SubPlan from wich the POperations should be extracted + * @return list of POperations extracted from the plan + */ + public static List createOperationsList(SubPlan plan) { + List operationsList = new ArrayList<>(); + while (plan.getParentPlans().size() > 0) { + operationsList.add(plan.getOperation()); + SubPlan parentPlan = plan.getParentPlans().get(0); + plan = parentPlan; + } + operationsList.add(plan.getOperation()); + + Collections.reverse(operationsList); + return operationsList; + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/util/OperationCostComparator.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/util/OperationCostComparator.java new file mode 100644 index 00000000..58f4fc41 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/util/OperationCostComparator.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Marton Bur, Zoltan Ujhelyi, Akos Horvath, Istvan Rath and Danil Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.planner.util; + +import java.util.Comparator; + +import tools.refinery.viatra.runtime.localsearch.planner.PConstraintInfo; + +/** + * @author Marton Bur + * + */ +public class OperationCostComparator implements Comparator{ + + @Override + public int compare(PConstraintInfo o1, PConstraintInfo o2) { + return Double.compare(o1.getCost(), o2.getCost()); + } + +} diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/profiler/LocalSearchProfilerAdapter.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/profiler/LocalSearchProfilerAdapter.java new file mode 100644 index 00000000..8c50c694 --- /dev/null +++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/profiler/LocalSearchProfilerAdapter.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Grill Balázs, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.localsearch.profiler; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import tools.refinery.viatra.runtime.localsearch.MatchingFrame; +import tools.refinery.viatra.runtime.localsearch.matcher.ILocalSearchAdapter; +import tools.refinery.viatra.runtime.localsearch.matcher.LocalSearchMatcher; +import tools.refinery.viatra.runtime.localsearch.matcher.MatcherReference; +import tools.refinery.viatra.runtime.localsearch.operations.ISearchOperation; +import tools.refinery.viatra.runtime.localsearch.plan.SearchPlan; +import tools.refinery.viatra.runtime.localsearch.plan.SearchPlanExecutor; + +/** + * This is a simple {@link ILocalSearchAdapter} which capable of counting + * each search operation execution then printing it in human readably form + * (along with the executed plans) using {@link #toString()} + * @author Grill Balázs + * @since 1.5 + * + */ +public class LocalSearchProfilerAdapter implements ILocalSearchAdapter { + + private final Map> planReference = new HashMap<>(); + + private final Map successfulOperationCounts = new HashMap<>(); + private final Map failedOperationCounts = new HashMap<>(); + + @Override + public void patternMatchingStarted(LocalSearchMatcher lsMatcher) { + MatcherReference key = new MatcherReference(lsMatcher.getPlanDescriptor().getQuery(), + lsMatcher.getPlanDescriptor().getAdornment()); + planReference.put(key, lsMatcher.getPlan().stream().map(SearchPlanExecutor::getSearchPlan).collect(Collectors.toList())); + } + + @Override + public void operationExecuted(SearchPlan plan, ISearchOperation operation, MatchingFrame frame, boolean isSuccessful) { + Map counts = isSuccessful ? successfulOperationCounts : failedOperationCounts; + counts.merge(operation, + /*no previous entry*/1, + /*increase previous value*/(oldValue, v) -> oldValue + 1); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (java.util.Map.Entry> entry: planReference.entrySet()){ + sb.append(entry.getKey()); + sb.append("\n"); + + sb.append(entry.getValue()); + + List bodies = entry.getValue(); + sb.append("{\n"); + for(int i=0;i 0); + final int failCount = failedOperationCounts.computeIfAbsent(operation, op -> 0); + sb.append("\t\t");sb.append(successCount); + sb.append("\t");sb.append(failCount); + sb.append("\t");sb.append(successCount + failCount); + sb.append("\t");sb.append(operation); + sb.append("\n"); + } + sb.append("\t)\n"); + } + sb.append("}\n"); + } + return sb.toString(); + } + +} diff --git a/subprojects/viatra-runtime-matchers/about.html b/subprojects/viatra-runtime-matchers/about.html new file mode 100644 index 00000000..d1d5593a --- /dev/null +++ b/subprojects/viatra-runtime-matchers/about.html @@ -0,0 +1,26 @@ + + + + +About + + + +

    About This Content

    + +

    March 18, 2019

    +

    License

    + +

    The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise indicated below, the Content is provided to you under the terms and conditions of the +Eclipse Public License Version 2.0 ("EPL"). A copy of the EPL is available at http://www.eclipse.org/legal/epl-v20.html. +For purposes of the EPL, "Program" will mean the Content.

    + +

    If you did not receive this Content directly from the Eclipse Foundation, the Content is being redistributed by another party ("Redistributor") and different terms and conditions may +apply to your use of any object code in the Content. Check the Redistributor's license that was provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise +indicated below, the terms and conditions of the EPL still apply to any source code in the Content and such source code may be obtained at http://www.eclipse.org.

    + + diff --git a/subprojects/viatra-runtime-matchers/build.gradle.kts b/subprojects/viatra-runtime-matchers/build.gradle.kts new file mode 100644 index 00000000..20a8c2c3 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/build.gradle.kts @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ + +plugins { + id("tools.refinery.gradle.java-library") +} + +dependencies { + implementation(libs.slf4j.log4j) + implementation(libs.eclipseCollections.api) + implementation(libs.eclipseCollections) +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/ViatraQueryRuntimeException.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/ViatraQueryRuntimeException.java new file mode 100644 index 00000000..83f6f766 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/ViatraQueryRuntimeException.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers; + +/** + * A common base class for all exceptions thrown by various VIATRA Query Runtime APIs. + * + * @author Zoltan Ujhelyi + * @since 2.0 + */ +public abstract class ViatraQueryRuntimeException extends RuntimeException { + + private static final long serialVersionUID = -8505253058035069310L; + + public ViatraQueryRuntimeException() { + super(); + } + + public ViatraQueryRuntimeException(String message) { + super(message); + } + + public ViatraQueryRuntimeException(Throwable cause) { + super(cause); + } + + public ViatraQueryRuntimeException(String message, Throwable cause) { + super(message, cause); + } + + public ViatraQueryRuntimeException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/AverageAccumulator.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/AverageAccumulator.java new file mode 100644 index 00000000..2c09ede1 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/AverageAccumulator.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.aggregators; + +/** + * @since 2.0 + */ +class AverageAccumulator { + Domain value; + long count; + + public AverageAccumulator(Domain value, long count) { + super(); + this.value = value; + this.count = count; + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/DoubleAverageOperator.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/DoubleAverageOperator.java new file mode 100644 index 00000000..e8a26afd --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/DoubleAverageOperator.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.aggregators; + +import java.util.OptionalDouble; +import java.util.stream.Stream; + +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; + +/** + * @author Zoltan Ujhelyi + * @since 2.0 + */ +public class DoubleAverageOperator implements IMultisetAggregationOperator, Double> { + + public static final DoubleAverageOperator INSTANCE = new DoubleAverageOperator(); + + private DoubleAverageOperator() { + // Singleton, do not call. + } + + @Override + public String getShortDescription() { + return "avg incrementally computes the average of java.lang.Integer values"; + } + + @Override + public String getName() { + return "avg"; + } + + @Override + public AverageAccumulator createNeutral() { + return new AverageAccumulator(0d, 0l); + } + + @Override + public boolean isNeutral(AverageAccumulator result) { + return result.count == 0l; + } + + @Override + public AverageAccumulator update(AverageAccumulator oldResult, Double updateValue, + boolean isInsertion) { + if (isInsertion) { + oldResult.value += updateValue; + oldResult.count++; + } else { + oldResult.value -= updateValue; + oldResult.count--; + } + return oldResult; + } + + @Override + public Double getAggregate(AverageAccumulator result) { + return (result.count == 0) + ? null + : result.value/result.count; + } + + @Override + public Double aggregateStream(Stream stream) { + final OptionalDouble averageOpt = stream.mapToDouble(Double::doubleValue).average(); + return averageOpt.isPresent() ? averageOpt.getAsDouble() : null; + } + + /** + * @since 2.4 + */ + @Override + public AverageAccumulator clone(AverageAccumulator original) { + return new AverageAccumulator(original.value, original.count); + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/DoubleSumOperator.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/DoubleSumOperator.java new file mode 100644 index 00000000..744b0cd1 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/DoubleSumOperator.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.aggregators; + +import java.util.stream.Stream; + +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.AbstractMemorylessAggregationOperator; + +/** + * Incrementally computes the sum of java.lang.Double values + * @author Gabor Bergmann + * @since 1.4 + */ +public class DoubleSumOperator extends AbstractMemorylessAggregationOperator { + public static final DoubleSumOperator INSTANCE = new DoubleSumOperator(); + + private DoubleSumOperator() { + // Singleton, do not call. + } + + @Override + public String getShortDescription() { + return "sum incrementally computes the sum of java.lang.Double values"; + } + @Override + public String getName() { + return "sum"; + } + + @Override + public Double createNeutral() { + return 0d; + } + + @Override + public boolean isNeutral(Double result) { + return createNeutral().equals(result); + } + + @Override + public Double update(Double oldResult, Double updateValue, boolean isInsertion) { + return isInsertion ? + oldResult + updateValue : + oldResult - updateValue; + } + + /** + * @since 2.0 + */ + @Override + public Double aggregateStream(Stream stream) { + return stream.mapToDouble(Double::doubleValue).sum(); + } + + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/ExtremumOperator.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/ExtremumOperator.java new file mode 100644 index 00000000..ee4ceeb8 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/ExtremumOperator.java @@ -0,0 +1,135 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.aggregators; + +import java.util.Comparator; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.stream.Stream; + +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; + +/** + * Incrementally computes the minimum or maximum of java.lang.Comparable values, using the default comparison + * + * @author Gabor Bergmann + * @since 1.4 + */ +public class ExtremumOperator> + implements IMultisetAggregationOperator, T> { + + public enum Extreme { + MIN, MAX; + + /** + * @since 2.0 + */ + public T pickFrom(SortedMap nonEmptyMultiSet) { + switch(this) { + case MIN: + return nonEmptyMultiSet.firstKey(); + case MAX: + return nonEmptyMultiSet.lastKey(); + default: + return null; + } + } + } + + private static final ExtremumOperator MIN_OP = new ExtremumOperator<>(Extreme.MIN); + private static final ExtremumOperator MAX_OP = new ExtremumOperator<>(Extreme.MAX); + + public static > ExtremumOperator getMin() { + return MIN_OP; + } + public static > ExtremumOperator getMax() { + return MAX_OP; + } + + Extreme extreme; + private ExtremumOperator(Extreme extreme) { + super(); + this.extreme = extreme; + } + + @Override + public String getShortDescription() { + String opName = getName(); + return String.format( + "%s incrementally computes the %simum of java.lang.Comparable values, using the default comparison", + opName, opName); + } + + @Override + public String getName() { + return extreme.name().toLowerCase(); + } + + /** + * @since 2.0 + */ + @Override + public SortedMap createNeutral() { + return new TreeMap<>(); + } + + /** + * @since 2.0 + */ + @Override + public boolean isNeutral(SortedMap result) { + return result.isEmpty(); + } + + /** + * @since 2.0 + */ + @Override + public SortedMap update(SortedMap oldResult, T updateValue, boolean isInsertion) { + oldResult.compute(updateValue, (value, c) -> { + int count = (c == null) ? 0 : c; + int result = (isInsertion) ? count+1 : count-1; + return (result == 0) ? null : result; + }); + return oldResult; + } + + /** + * @since 2.0 + */ + @Override + public T getAggregate(SortedMap result) { + return result.isEmpty() ? null : + extreme.pickFrom(result); + } + + /** + * @since 2.0 + */ + @Override + public T aggregateStream(Stream stream) { + switch (extreme) { + case MIN: + return stream.min(Comparator.naturalOrder()).orElse(null); + case MAX: + return stream.max(Comparator.naturalOrder()).orElse(null); + default: + return null; + } + } + + /** + * @since 2.4 + */ + @Override + public SortedMap clone(SortedMap original) { + return new TreeMap(original); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/IntegerAverageOperator.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/IntegerAverageOperator.java new file mode 100644 index 00000000..bf422476 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/IntegerAverageOperator.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.aggregators; + +import java.util.OptionalDouble; +import java.util.stream.Stream; + +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; + +/** + * @author Zoltan Ujhelyi + * @since 2.0 + */ +public class IntegerAverageOperator implements IMultisetAggregationOperator, Double> { + + public static final IntegerAverageOperator INSTANCE = new IntegerAverageOperator(); + + private IntegerAverageOperator() { + // Singleton, do not call. + } + + @Override + public String getShortDescription() { + return "avg incrementally computes the average of java.lang.Integer values"; + } + + @Override + public String getName() { + return "avg"; + } + + @Override + public AverageAccumulator createNeutral() { + return new AverageAccumulator(0, 0l); + } + + @Override + public boolean isNeutral(AverageAccumulator result) { + return result.count == 0l; + } + + @Override + public AverageAccumulator update(AverageAccumulator oldResult, Integer updateValue, + boolean isInsertion) { + if (isInsertion) { + oldResult.value += updateValue; + oldResult.count++; + } else { + oldResult.value -= updateValue; + oldResult.count--; + } + return oldResult; + } + + @Override + public Double getAggregate(AverageAccumulator result) { + return (result.count == 0) + ? null + : ((double)result.value)/result.count; + } + + @Override + public Double aggregateStream(Stream stream) { + final OptionalDouble averageOpt = stream.mapToInt(Integer::intValue).average(); + return averageOpt.isPresent() ? averageOpt.getAsDouble() : null; + } + + /** + * @since 2.4 + */ + @Override + public AverageAccumulator clone(AverageAccumulator original) { + return new AverageAccumulator(original.value, original.count); + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/IntegerSumOperator.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/IntegerSumOperator.java new file mode 100644 index 00000000..18584256 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/IntegerSumOperator.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.aggregators; + +import java.util.stream.Stream; + +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.AbstractMemorylessAggregationOperator; + +/** + * Incrementally computes the sum of java.lang.Integer values + * @author Gabor Bergmann + * @since 1.4 + */ +public class IntegerSumOperator extends AbstractMemorylessAggregationOperator { + public static final IntegerSumOperator INSTANCE = new IntegerSumOperator(); + + private IntegerSumOperator() { + // Singleton, do not call. + } + + @Override + public String getShortDescription() { + return "sum incrementally computes the sum of java.lang.Integer values"; + } + @Override + public String getName() { + return "sum"; + } + + @Override + public Integer createNeutral() { + return 0; + } + + @Override + public boolean isNeutral(Integer result) { + return createNeutral().equals(result); + } + + @Override + public Integer update(Integer oldResult, Integer updateValue, boolean isInsertion) { + return isInsertion ? + oldResult + updateValue : + oldResult - updateValue; + } + + /** + * @since 2.0 + */ + @Override + public Integer aggregateStream(Stream stream) { + return stream.mapToInt(Integer::intValue).sum(); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/LongAverageOperator.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/LongAverageOperator.java new file mode 100644 index 00000000..d56c9507 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/LongAverageOperator.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.aggregators; + +import java.util.OptionalDouble; +import java.util.stream.Stream; + +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; + +/** + * @author Zoltan Ujhelyi + * @since 2.0 + */ +public class LongAverageOperator implements IMultisetAggregationOperator, Double> { + + public static final LongAverageOperator INSTANCE = new LongAverageOperator(); + + private LongAverageOperator() { + // Singleton, do not call. + } + + @Override + public String getShortDescription() { + return "avg incrementally computes the average of java.lang.Integer values"; + } + + @Override + public String getName() { + return "avg"; + } + + @Override + public AverageAccumulator createNeutral() { + return new AverageAccumulator(0l, 0l); + } + + @Override + public boolean isNeutral(AverageAccumulator result) { + return result.count == 0l; + } + + @Override + public AverageAccumulator update(AverageAccumulator oldResult, Long updateValue, + boolean isInsertion) { + if (isInsertion) { + oldResult.value += updateValue; + oldResult.count++; + } else { + oldResult.value -= updateValue; + oldResult.count--; + } + return oldResult; + } + + @Override + public Double getAggregate(AverageAccumulator result) { + return (result.count == 0) + ? null + : ((double)result.value)/result.count; + } + + @Override + public Double aggregateStream(Stream stream) { + final OptionalDouble averageOpt = stream.mapToLong(Long::longValue).average(); + return averageOpt.isPresent() ? averageOpt.getAsDouble() : null; + } + + /** + * @since 2.4 + */ + @Override + public AverageAccumulator clone(AverageAccumulator original) { + return new AverageAccumulator(original.value, original.count); + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/LongSumOperator.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/LongSumOperator.java new file mode 100644 index 00000000..29ded090 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/LongSumOperator.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.aggregators; + +import java.util.stream.Stream; + +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.AbstractMemorylessAggregationOperator; + +/** + * Incrementally computes the sum of java.lang.Long values + * @author Gabor Bergmann + * @since 1.4 + */ +public class LongSumOperator extends AbstractMemorylessAggregationOperator { + public static final LongSumOperator INSTANCE = new LongSumOperator(); + + private LongSumOperator() { + // Singleton, do not call. + } + + @Override + public String getShortDescription() { + return "sum incrementally computes the sum of java.lang.Long values"; + } + @Override + public String getName() { + return "sum"; + } + + @Override + public Long createNeutral() { + return 0L; + } + + @Override + public boolean isNeutral(Long result) { + return createNeutral().equals(result); + } + + @Override + public Long update(Long oldResult, Long updateValue, boolean isInsertion) { + return isInsertion ? + oldResult + updateValue : + oldResult - updateValue; + } + + /** + * @since 2.0 + */ + @Override + public Long aggregateStream(Stream stream) { + return stream.mapToLong(Long::longValue).sum(); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/avg.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/avg.java new file mode 100644 index 00000000..c25678aa --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/avg.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.aggregators; + +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.AggregatorType; +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.BoundAggregator; +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IAggregatorFactory; + +/** + * This aggregator calculates the average of the values of a selected aggregate parameter of a called pattern. The aggregate + * parameter is selected with the '#' symbol; the aggregate parameter must not be used outside the aggregator call. The + * other parameters of the call might be bound or unbound; bound parameters limit the matches to consider for the + * summation. + * + * @since 2.0 + * + */ +@AggregatorType( + parameterTypes = {Integer.class, Double.class, Long.class}, + returnTypes = {Double.class, Double.class, Double.class}) +public final class avg implements IAggregatorFactory { + + @Override + public BoundAggregator getAggregatorLogic(Class domainClass) { + if (Integer.class.equals(domainClass)) + return new BoundAggregator(IntegerAverageOperator.INSTANCE, Integer.class, Double.class); + if (Double.class.equals(domainClass)) + return new BoundAggregator(DoubleAverageOperator.INSTANCE, Double.class, Double.class); + if (Long.class.equals(domainClass)) + return new BoundAggregator(LongAverageOperator.INSTANCE, Long.class, Double.class); + else throw new IllegalArgumentException(); + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/count.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/count.java new file mode 100644 index 00000000..8310a0ce --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/count.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.aggregators; + +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.AggregatorType; +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.BoundAggregator; +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IAggregatorFactory; + +/** + * An aggregator to count the number of matches a pattern has. The return of the aggregator is an non-negative integer + * number. + * + * @since 1.4 + * + */ +@AggregatorType(parameterTypes = {Void.class}, returnTypes = {Integer.class}) +public final class count implements IAggregatorFactory { + + @Override + public BoundAggregator getAggregatorLogic(Class domainClass) { + if (Void.class.equals(domainClass)) + return new BoundAggregator(null, Void.class, Integer.class); + else throw new IllegalArgumentException(); + } + + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/max.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/max.java new file mode 100644 index 00000000..e0236223 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/max.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.aggregators; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Calendar; +import java.util.Date; + +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.AggregatorType; +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.BoundAggregator; +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IAggregatorFactory; + +/** + * This aggregator calculates the maximum value of a selected aggregate parameter of a called pattern. The aggregate + * parameter is selected with the '#' symbol; the aggregate parameter must not be used outside the aggregator call. The + * other parameters of the call might be bound or unbound; bound parameters limit the matches to consider for the + * minimum calculation. + * + * @since 1.4 + * @author Gabor Bergmann + */ +@AggregatorType( + // TODO T extends Comparable? + parameterTypes = {BigDecimal.class, BigInteger.class, Boolean.class, Byte.class, Calendar.class, Character.class, + Date.class, Double.class, Enum.class, Float.class, Integer.class, Long.class, Short.class, String.class}, + returnTypes = {BigDecimal.class, BigInteger.class, Boolean.class, Byte.class, Calendar.class, Character.class, + Date.class, Double.class, Enum.class, Float.class, Integer.class, Long.class, Short.class, String.class}) +public final class max implements IAggregatorFactory { + + @Override + public BoundAggregator getAggregatorLogic(Class domainClass) { + if (Comparable.class.isAssignableFrom(domainClass)) + return new BoundAggregator(ExtremumOperator.getMax(), domainClass, domainClass); + else throw new IllegalArgumentException(); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/min.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/min.java new file mode 100644 index 00000000..6408c57b --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/min.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.aggregators; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Calendar; +import java.util.Date; + +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.AggregatorType; +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.BoundAggregator; +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IAggregatorFactory; + +/** + * This aggregator calculates the minimum value of a selected aggregate parameter of a called pattern. The aggregate + * parameter is selected with the '#' symbol; the aggregate parameter must not be used outside the aggregator call. The + * other parameters of the call might be bound or unbound; bound parameters limit the matches to consider for the + * minimum calculation. + * + * @since 1.4 + * + */ +@AggregatorType( + // TODO T extends Comparable? + parameterTypes = {BigDecimal.class, BigInteger.class, Boolean.class, Byte.class, Calendar.class, Character.class, + Date.class, Double.class, Enum.class, Float.class, Integer.class, Long.class, Short.class, String.class}, + returnTypes = {BigDecimal.class, BigInteger.class, Boolean.class, Byte.class, Calendar.class, Character.class, + Date.class, Double.class, Enum.class, Float.class, Integer.class, Long.class, Short.class, String.class}) +public final class min implements IAggregatorFactory { + + @Override + public BoundAggregator getAggregatorLogic(Class domainClass) { + if (Comparable.class.isAssignableFrom(domainClass)) + return new BoundAggregator(ExtremumOperator.getMin(), domainClass, domainClass); + else throw new IllegalArgumentException(); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/sum.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/sum.java new file mode 100644 index 00000000..69ff2e75 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/aggregators/sum.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.aggregators; + +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.AggregatorType; +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.BoundAggregator; +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IAggregatorFactory; + +/** + * This aggregator calculates the sum of the values of a selected aggregate parameter of a called pattern. The aggregate + * parameter is selected with the '#' symbol; the aggregate parameter must not be used outside the aggregator call. The + * other parameters of the call might be bound or unbound; bound parameters limit the matches to consider for the + * summation. + * + * @since 1.4 + * + */ +@AggregatorType( + parameterTypes = {Integer.class, Double.class, Long.class}, + returnTypes = {Integer.class, Double.class, Long.class}) +public final class sum implements IAggregatorFactory { + + @Override + public BoundAggregator getAggregatorLogic(Class domainClass) { + if (Integer.class.equals(domainClass)) + return new BoundAggregator(IntegerSumOperator.INSTANCE, Integer.class, Integer.class); + if (Double.class.equals(domainClass)) + return new BoundAggregator(DoubleSumOperator.INSTANCE, Double.class, Double.class); + if (Long.class.equals(domainClass)) + return new BoundAggregator(LongSumOperator.INSTANCE, Long.class, Long.class); + else throw new IllegalArgumentException(); + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/algorithms/OrderedIterableMerge.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/algorithms/OrderedIterableMerge.java new file mode 100644 index 00000000..1917e909 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/algorithms/OrderedIterableMerge.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.algorithms; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * @author Gabor Bergmann + * @since 2.1 + * + */ +public class OrderedIterableMerge { + + private OrderedIterableMerge() { + // Hidden utility class constructor + } + + /** + * Lazily merges two iterables, each ordered according to a given comparator. + * Retains order in the result, and also eliminates any duplicates that appear in both arguments. + */ + public static Iterable mergeUniques(Iterable first, Iterable second, Comparator comparator) { + return () -> new Iterator() { + Iterator firstIterator = first.iterator(); + Iterator secondIterator = second.iterator(); + T firstItem; + T secondItem; + + { + fetchFirst(); + fetchSecond(); + } + + private T fetchFirst() { + T previous = firstItem; + if (firstIterator.hasNext()) + firstItem = firstIterator.next(); + else + firstItem = null; + return previous; + } + private T fetchSecond() { + T previous = secondItem; + if (secondIterator.hasNext()) + secondItem = secondIterator.next(); + else + secondItem = null; + return previous; + } + + @Override + public boolean hasNext() { + return firstItem != null || secondItem != null; + } + @Override + public T next() { + if (!hasNext()) throw new NoSuchElementException(); + if (firstItem != null && secondItem != null) { + if (secondItem == firstItem) { // duplicates + fetchFirst(); + return fetchSecond(); + } else if (comparator.compare(firstItem, secondItem) < 0) { + return fetchFirst(); + } else { + return fetchSecond(); + } + } else if (firstItem != null) { + return fetchFirst(); + } else { // secondItem must be non-null + return fetchSecond(); + } + } + }; + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/algorithms/UnionFind.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/algorithms/UnionFind.java new file mode 100644 index 00000000..c69f08e5 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/algorithms/UnionFind.java @@ -0,0 +1,214 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.algorithms; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; + +/** + * Union-find data structure implementation. Note that the implementation relies on the correct implementation of the + * equals method of the type parameter's class. + * + * @author Tamas Szabo + * + * @param + * the type parameter of the element's stored in the union-find data structure + */ +public class UnionFind { + + private final Map> nodeMap; + final Map> setMap; + + /** + * Instantiate a new union-find data structure. + */ + public UnionFind() { + nodeMap = CollectionsFactory.createMap(); + setMap = CollectionsFactory.createMap(); + } + + /** + * Instantiate a new union-find data structure with the given elements as separate sets. + */ + public UnionFind(Iterable elements) { + this(); + for (V element : elements) { + makeSet(element); + } + } + + /** + * Creates a new union set from a collection of elements. + * + * @param nodes + * the collection of elements + * @return the root element + */ + public V makeSet(Collection nodes) { + if (!nodes.isEmpty()) { + Iterator iterator = nodes.iterator(); + V root = makeSet(iterator.next()); + while (iterator.hasNext()) { + root = union(root, iterator.next()); + } + return root; + } else { + return null; + } + } + + /** + * This method creates a single set containing the given node. + * + * @param node + * the root node of the set + * @return the root element + */ + public V makeSet(V node) { + if (!nodeMap.containsKey(node)) { + UnionFindNodeProperty prop = new UnionFindNodeProperty(0, node); + nodeMap.put(node, prop); + Set set = new HashSet(); + set.add(node); + setMap.put(node, set); + } + return node; + } + + /** + * Find method with path compression. + * + * @param node + * the node to find + * @return the root node of the set in which the given node can be found + */ + public V find(V node) { + UnionFindNodeProperty prop = nodeMap.get(node); + + if (prop != null) { + if (prop.parent.equals(node)) { + return node; + } else { + prop.parent = find(prop.parent); + return prop.parent; + } + } + return null; + } + + /** + * Union by rank implementation of the two sets which contain x and y; x and/or y can be a single element from the + * universe. + * + * @param x + * set or single element of the universe + * @param y + * set or single element of the universe + * @return the new root of the two sets + */ + public V union(V x, V y) { + V xRoot = find(x); + V yRoot = find(y); + + if ((xRoot == null) || (yRoot == null)) { + return union( (xRoot == null) ? makeSet(x) : xRoot, (yRoot == null) ? makeSet(y) : yRoot); + } + else if (!xRoot.equals(yRoot)) { + UnionFindNodeProperty xRootProp = nodeMap.get(xRoot); + UnionFindNodeProperty yRootProp = nodeMap.get(yRoot); + + if (xRootProp.rank < yRootProp.rank) { + xRootProp.parent = yRoot; + setMap.get(yRoot).addAll(setMap.get(xRoot)); + setMap.remove(xRoot); + return yRoot; + } else {// (xRootProp.rank >= yRootProp.rank) + yRootProp.parent = xRoot; + yRootProp.rank = (xRootProp.rank == yRootProp.rank) ? yRootProp.rank + 1 : yRootProp.rank; + setMap.get(xRoot).addAll(setMap.get(yRoot)); + setMap.remove(yRoot); + return xRoot; + } + } else { + return xRoot; + } + } + + /** + * Places the given elements in to the same partition. + */ + public void unite(Set elements) { + if (elements.size() > 1) { + V current = null; + for (V element : elements) { + if (current != null) { + if (getPartition(element) != null) { + union(current, element); + } + } else { + if (getPartition(element) != null) { + current = element; + } + } + } + } + } + + /** + * Delete the set whose root is the given node. + * + * @param root + * the root node + */ + public void deleteSet(V root) { + // if (setMap.containsKey(root)) + for (V n : setMap.get(root)) { + nodeMap.remove(n); + } + setMap.remove(root); + } + + /** + * Returns if all given elements are in the same partition. + */ + public boolean isSameUnion(Set elements) { + for (Set partition : setMap.values()) { + if (partition.containsAll(elements)) { + return true; + } + } + return false; + } + + + /** + * Returns the partition in which the given element can be found, or null otherwise. + */ + public Set getPartition(V element) { + V root = find(element); + return setMap.get(root); + } + + /** + * Returns all partitions. + */ + public Collection> getPartitions() { + return setMap.values(); + } + + public Set getPartitionHeads() { + return setMap.keySet(); + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/algorithms/UnionFindNodeProperty.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/algorithms/UnionFindNodeProperty.java new file mode 100644 index 00000000..82852f9c --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/algorithms/UnionFindNodeProperty.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.algorithms; + +public class UnionFindNodeProperty { + + public int rank; + public V parent; + + public UnionFindNodeProperty() { + this.rank = 0; + this.parent = null; + } + + public UnionFindNodeProperty(int rank, V parent) { + super(); + this.rank = rank; + this.parent = parent; + } + + @Override + public String toString() { + return "[rank:" + rank + ", parent:" + parent.toString() + "]"; + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/CommonQueryHintOptions.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/CommonQueryHintOptions.java new file mode 100644 index 00000000..317293bf --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/CommonQueryHintOptions.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Grill Balázs, IncQueryLabs + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.backend; + +import tools.refinery.viatra.runtime.matchers.psystem.rewriters.IRewriterTraceCollector; +import tools.refinery.viatra.runtime.matchers.psystem.rewriters.NopTraceCollector; + +/** + * Query evaluation hints applicable to any engine + * @since 1.6 + * + */ +public final class CommonQueryHintOptions { + + private CommonQueryHintOptions() { + // Hiding constructor for utility class + } + + /** + * This hint instructs the query backends to record trace information into the given trace collector + */ + public static final QueryHintOption normalizationTraceCollector = + hintOption("normalizationTraceCollector", NopTraceCollector.INSTANCE); + + // internal helper for conciseness + private static QueryHintOption hintOption(String hintKeyLocalName, T defaultValue) { + return new QueryHintOption<>(CommonQueryHintOptions.class, hintKeyLocalName, defaultValue); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/ICallDelegationStrategy.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/ICallDelegationStrategy.java new file mode 100644 index 00000000..40853f46 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/ICallDelegationStrategy.java @@ -0,0 +1,89 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.backend; + +import tools.refinery.viatra.runtime.matchers.context.IQueryResultProviderAccess; +import tools.refinery.viatra.runtime.matchers.psystem.IQueryReference; +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; + +/** + * Function object that specifies how hints (including backend preferences) shall propagate through pattern calls. + * + *

    A few useful default implementations are included as static fields. + * + *

    As of 2.1, only suppported by the local search backend, and only if the pattern call is not flattened. + * + * @author Gabor Bergmann + * @since 2.1 + */ +@FunctionalInterface +public interface ICallDelegationStrategy { + + /** + * Specifies how hints (including backend preferences) shall propagate through pattern calls. + * + * @param call a {@link PConstraint} in a query that calls another query. + * @param callerHint a hint under which the calling pattern is evaluated, + * @param callerBackend the actual backend evaluating the calling pattern. + * @param calleeHintProvider the provider of hints for the called pattern. + * @return the hints, including backend selection, + * that the backend responsible for the caller pattern must specify when + * requesting the {@link IQueryResultProvider} for the called pattern via {@link IQueryResultProviderAccess}. + */ + public QueryEvaluationHint transformHints(IQueryReference call, + QueryEvaluationHint callerHint, + IQueryBackend callerBackend, + IQueryBackendHintProvider calleeHintProvider); + + + /** + * Options known for callee are used to override caller options, except the backend selection. + * Always use the same backend for the callee and the caller, regardless what is specified for the callee pattern. + * @author Gabor Bergmann + */ + public static final ICallDelegationStrategy FULL_BACKEND_ADHESION = (call, callerHint, callerBackend, calleeHintProvider) -> { + QueryEvaluationHint calleeHint = + calleeHintProvider.getQueryEvaluationHint(call.getReferredQuery()); + QueryEvaluationHint result = + callerHint == null ? calleeHint : callerHint.overrideBy(calleeHint); + + QueryEvaluationHint backendAdhesion = new QueryEvaluationHint( + null /* settings-ignorant */, callerBackend.getFactory()); + result = result.overrideBy(backendAdhesion); + return result; + }; + /** + * Options known for callee are used to override caller options, including the backend selection. + * If callee does not specify a backend requirement, the backend of the caller is kept. + * @author Gabor Bergmann + */ + public static final ICallDelegationStrategy PARTIAL_BACKEND_ADHESION = (call, callerHint, callerBackend, calleeHintProvider) -> { + QueryEvaluationHint backendAdhesion = new QueryEvaluationHint( + null /* settings-ignorant */, callerBackend.getFactory()); + + QueryEvaluationHint result = + callerHint == null ? backendAdhesion : callerHint.overrideBy(backendAdhesion); + + QueryEvaluationHint calleeHint = calleeHintProvider.getQueryEvaluationHint(call.getReferredQuery()); + result = result.overrideBy(calleeHint); + + return result; + }; + /** + * Options known for callee are used to override caller options, including the backend selection. + * Always use the backend specified for the callee (or the default if none), regardless of the backend of the caller. + * @author Gabor Bergmann + */ + public static final ICallDelegationStrategy NO_BACKEND_ADHESION = (call, callerHint, callerBackend, calleeHintProvider) -> { + QueryEvaluationHint calleeHint = calleeHintProvider.getQueryEvaluationHint(call.getReferredQuery()); + return callerHint == null ? calleeHint : callerHint.overrideBy(calleeHint); + }; + + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IMatcherCapability.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IMatcherCapability.java new file mode 100644 index 00000000..104b68a8 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IMatcherCapability.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Grill Balázs, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.backend; + +/** + * Implementations of this interface can be used to decide whether a matcher created by an arbitrary backend can + * potentially be used as a substitute for another matcher. + * + * @author Grill Balázs + * @since 1.4 + * + */ +public interface IMatcherCapability { + + /** + * Returns true if matchers of this capability can be used as a substitute for a matcher implementing the given capability + */ + public boolean canBeSubstitute(IMatcherCapability capability); + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryBackend.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryBackend.java new file mode 100644 index 00000000..c85f10a4 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryBackend.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.backend; + +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; + +/** + * Internal interface for a VIATRA query specification. Each query is associated with a pattern. Methods instantiate a matcher + * of the pattern with various parameters. + * + * @author Bergmann Gábor + * @since 0.9 + * @noextend This interface is not intended to be extended by users of the VIATRA framework, and should only be used by the query engine + */ +public interface IQueryBackend { + + /** + * @return true iff this backend is incremental, i.e. it caches the results of queries for quick retrieval, + * and can provide update notifications on result set changes. + */ + public boolean isCaching(); + + /** + * Returns a result provider for a given query. Repeated calls may return the same instance. + * @throws ViatraQueryRuntimeException + */ + public IQueryResultProvider getResultProvider(PQuery query); + + /** + * Returns a result provider for a given query. Repeated calls may return the same instance. + * @param optional hints that may override engine and query defaults (as provided by {@link IQueryBackendHintProvider}). Can be null. + * @throws ViatraQueryRuntimeException + * @since 1.4 + */ + public IQueryResultProvider getResultProvider(PQuery query, QueryEvaluationHint hints); + + /** + * Returns an existing result provider for a given query, if it was previously constructed, returns null otherwise. + * Will not construct and initialize new result providers. + */ + public IQueryResultProvider peekExistingResultProvider(PQuery query); + + /** + * Propagates all pending updates in this query backend. The implementation of this method is optional, and it + * can be ignored entirely if the backend does not delay updates. + * @since 1.6 + */ + public void flushUpdates(); + + /** + * Disposes the query backend. + */ + public abstract void dispose(); + + /** + * @return the factory that created this backend, if this functionality is supported + * @since 2.1 + */ + public IQueryBackendFactory getFactory(); + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryBackendFactory.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryBackendFactory.java new file mode 100644 index 00000000..e264ab3f --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryBackendFactory.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.backend; + +import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; + +/** + * A Query Backend Factory identifies a query evaluator implementation, and can create an evaluator instance (an {@link IQueryBackend}) tied to a specific VIATRA Query engine upon request. + * + *

    The factory is used as a lookup key for the backend instance, + * therefore implementors should either be singletons, or implement equals() / hashCode() accordingly. + * + * @author Bergmann Gabor + * + */ +public interface IQueryBackendFactory { + + /** + * Creates a new {@link IQueryBackend} instance tied to the given context elements. + * + * @return an instance of the class returned by {@link #getBackendClass()} that operates in the given context. + * @since 1.5 + */ + public IQueryBackend + create(IQueryBackendContext context); + + + /** + * The backend instances created by this factory are guaranteed to conform to the returned class. + */ + public Class getBackendClass(); + + /** + * Calculate the required capabilities, which are needed to execute the given pattern + * + * @since 1.4 + */ + public IMatcherCapability calculateRequiredCapability(PQuery query, QueryEvaluationHint hint); + + /** + * Returns whether the current backend is caching + * @since 2.0 + */ + public boolean isCaching(); +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryBackendFactoryProvider.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryBackendFactoryProvider.java new file mode 100644 index 00000000..8787814e --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryBackendFactoryProvider.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.backend; + +/** + * A provider interface for {@link IQueryBackendFactory} instances. + * @since 2.0 + */ +public interface IQueryBackendFactoryProvider { + + /** + * Returns a query backend factory instance. The method should return the same instance in case of repeated calls. + */ + IQueryBackendFactory getFactory(); + + /** + * Returns whether the given query backend should be considered as system default. If multiple backends are + * registered as system default, it is undefined which one will be chosen. + */ + default boolean isSystemDefaultEngine() { + return false; + } + + /** + * Returns whether the given query backend should be considered as system default search backend. If multiple + * backends are registered as system default, it is undefined which one will be chosen. + */ + default boolean isSystemDefaultSearchBackend() { + return false; + } + + + /** + * Returns whether the given query backend should be considered as system default caching backend. If multiple + * backends are registered as system default, it is undefined which one will be chosen. + */ + default boolean isSystemDefaultCachingBackend() { + return false; + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryBackendHintProvider.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryBackendHintProvider.java new file mode 100644 index 00000000..9bb76349 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryBackendHintProvider.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.backend; + +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; + +/** + * Provides query evaluation hints consisting of the Engine default hints and + * the hints provided by the pattern itself. + * + * @author Bergmann Gabor + * @since 0.9 + * @noimplement This interface is not intended to be implemented by clients, except in the tools.refinery.viatra.runtime plugin. + */ +public interface IQueryBackendHintProvider { + + /** + * Suggests query evaluation hints regarding a query. The returned hints reflects the default hints of the + * query engine merged with the hints provided by the pattern itself. These can be overridden via specific + * advanced API of the engine. + * + * @since 1.4 + */ + QueryEvaluationHint getQueryEvaluationHint(PQuery query); + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryResultProvider.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryResultProvider.java new file mode 100644 index 00000000..cd7d050f --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IQueryResultProvider.java @@ -0,0 +1,202 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.backend; + +import java.util.Optional; +import java.util.stream.Stream; + +import tools.refinery.viatra.runtime.matchers.planning.helpers.StatisticsHelper; +import tools.refinery.viatra.runtime.matchers.tuple.ITuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.Accuracy; + +/** + * An internal interface of the query backend that provides results of a given query. + * @author Bergmann Gabor + * @noimplement This interface is not intended to be implemented by clients. + */ +public interface IQueryResultProvider { + + /** + * Decides whether there are any matches of the pattern that conform to the given fixed values of some parameters. + * + * @param parameters + * array where each non-null element binds the corresponding pattern parameter to a fixed value. + * @pre size of input array must be equal to the number of parameters. + * @since 2.0 + */ + public boolean hasMatch(Object[] parameters); + + /** + * Decides whether there are any matches of the pattern that conform to the given fixed values of some parameters. + * + * @param parameterSeedMask + * a mask that extracts those parameters of the query (from the entire parameter list) that should be + * bound to a fixed value + * @param parameters + * the tuple of fixed values restricting the match set to be considered, in the same order as given in + * parameterSeedMask, so that for each considered match tuple, + * projectedParameterSeed.equals(parameterSeedMask.transform(match)) should hold + * @since 2.0 + */ + public boolean hasMatch(TupleMask parameterSeedMask, ITuple projectedParameterSeed); + + /** + * Returns the number of all matches of the pattern that conform to the given fixed values of some parameters. + * + * @param parameters + * array where each non-null element binds the corresponding pattern parameter to a fixed value. + * @pre size of input array must be equal to the number of parameters. + * @return the number of pattern matches found. + */ + public int countMatches(Object[] parameters); + + /** + * Returns the number of all matches of the pattern that conform to the given fixed values of some parameters. + * + * @param parameterSeedMask + * a mask that extracts those parameters of the query (from the entire parameter list) that should be + * bound to a fixed value + * @param parameters + * the tuple of fixed values restricting the match set to be considered, in the same order as given in + * parameterSeedMask, so that for each considered match tuple, + * projectedParameterSeed.equals(parameterSeedMask.transform(match)) should hold + * @return the number of pattern matches found. + * @since 1.7 + */ + public int countMatches(TupleMask parameterSeedMask, ITuple projectedParameterSeed); + + /** + * Gives an estimate of the number of different groups the matches are projected into by the given mask + * (e.g. for an identity mask, this means the full match set size). The estimate must meet the required accuracy. + * + *

    If there is insufficient information to provide an answer up to the required precision, {@link Optional#empty()} may be returned. + * In other words, query backends may deny an answer, or do their best to give an estimate without actually determining the match set of the query. + * However, caching backends are expected to simply return the indexed (projection) size, initialized on-demand if necessary. + * + *

    PRE: {@link TupleMask#isNonrepeating()} must hold for the group mask. + * + * @return if available, an estimate of the cardinality of the projection of the match set, with the desired accuracy. + * + * @since 2.1 + */ + public Optional estimateCardinality(TupleMask groupMask, Accuracy requiredAccuracy); + + /** + * Gives an estimate of the average size of different groups the matches are projected into by the given mask + * (e.g. for an identity mask, this means 1, while for an empty mask, the result is match set size). + * The estimate must meet the required accuracy. + * + *

    If there is insufficient information to provide an answer up to the required precision, {@link Optional#empty()} may be returned. + * In other words, query backends may deny an answer, or do their best to give an estimate without actually determining the match set of the query. + * However, caching backends are expected to simply return the exact value from the index, initialized on-demand if necessary. + * + *

    For an empty match set, zero is acceptable as an exact answer. + * + *

    PRE: {@link TupleMask#isNonrepeating()} must hold for the group mask. + * + * @return if available, an estimate of the average size of each projection group of the match set, with the desired accuracy. + * + * @since 2.1 + */ + public default Optional estimateAverageBucketSize(TupleMask groupMask, Accuracy requiredAccuracy) { + return StatisticsHelper.estimateAverageBucketSize(groupMask, requiredAccuracy, this::estimateCardinality); + } + + /** + * Returns an arbitrarily chosen match of the pattern that conforms to the given fixed values of some parameters. + * Neither determinism nor randomness of selection is guaranteed. + * + * @param parameters + * array where each non-null element binds the corresponding pattern parameter to a fixed value. + * @pre size of input array must be equal to the number of parameters. + * @return a match represented in the internal {@link Tuple} representation. + * @since 2.0 + */ + public Optional getOneArbitraryMatch(Object[] parameters); + + /** + * Returns an arbitrarily chosen match of the pattern that conforms to the given fixed values of some parameters. + * Neither determinism nor randomness of selection is guaranteed. + * + * @param parameterSeedMask + * a mask that extracts those parameters of the query (from the entire parameter list) that should be + * bound to a fixed value + * @param parameters + * the tuple of fixed values restricting the match set to be considered, in the same order as given in + * parameterSeedMask, so that for each considered match tuple, + * projectedParameterSeed.equals(parameterSeedMask.transform(match)) should hold + * @return a match represented in the internal {@link Tuple} representation. + * @since 2.0 + */ + public Optional getOneArbitraryMatch(TupleMask parameterSeedMask, ITuple parameters); + + /** + * Returns the set of all matches of the pattern that conform to the given fixed values of some parameters. + * + * @param parameters + * array where each non-null element binds the corresponding pattern parameter to a fixed value. + * @pre size of input array must be equal to the number of parameters. + * @return matches represented in the internal {@link Tuple} representation. + * @since 2.0 + */ + public Stream getAllMatches(Object[] parameters); + + /** + * Returns the set of all matches of the pattern that conform to the given fixed values of some parameters. + * + * @param parameterSeedMask + * a mask that extracts those parameters of the query (from the entire parameter list) that should be + * bound to a fixed value + * @param parameters + * the tuple of fixed values restricting the match set to be considered, in the same order as given in + * parameterSeedMask, so that for each considered match tuple, + * projectedParameterSeed.equals(parameterSeedMask.transform(match)) should hold + * @return matches represented in the internal {@link Tuple} representation. + * @since 2.0 + */ + public Stream getAllMatches(TupleMask parameterSeedMask, ITuple parameters); + + /** + * The underlying query evaluator backend. + */ + public IQueryBackend getQueryBackend(); + + /** + * Internal method that registers low-level callbacks for match appearance and disappearance. + * + *

    + * Caution: This is a low-level callback that is invoked when the pattern matcher is not necessarily in a + * consistent state yet. Importantly, no model modification permitted during the callback. + * + *

    + * The callback can be unregistered via invoking {@link #removeUpdateListener(Object)} with the same tag. + * + * @param listener + * the listener that will be notified of each new match that appears or disappears, starting from now. + * @param listenerTag + * a tag by which to identify the listener for later removal by {@link #removeUpdateListener(Object)}. + * @param fireNow + * if true, the insertion update allback will be immediately invoked on all current matches as a one-time effect. + * + * @throws UnsupportedOperationException if this is a non-incremental backend + * (i.e. {@link IQueryBackend#isCaching()} on {@link #getQueryBackend()} returns false) + */ + public void addUpdateListener(final IUpdateable listener, final Object listenerTag, boolean fireNow); + + /** + * Removes an existing listener previously registered with the given tag. + * + * @throws UnsupportedOperationException if this is a non-incremental backend + * (i.e. {@link IQueryBackend#isCaching()} on {@link #getQueryBackend()} returns false) + */ + public void removeUpdateListener(final Object listenerTag); + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IUpdateable.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IUpdateable.java new file mode 100644 index 00000000..baf7144a --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/IUpdateable.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.backend; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; + +/** + * Internal interface for the query backend to singal an update to a query result. + * @author Bergmann Gabor + * @since 0.9 + * + */ +public interface IUpdateable { + + /** + * This callback method must be free of exceptions, even {@link RuntimeException}s (though not {@link Error}s). + * @param updateElement the tuple that is changed + * @param isInsertion true if the tuple appeared in the result set, false if disappeared from the result set + */ + public void update(Tuple updateElement, boolean isInsertion); +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/QueryEvaluationHint.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/QueryEvaluationHint.java new file mode 100644 index 00000000..eab92128 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/QueryEvaluationHint.java @@ -0,0 +1,241 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.backend; + +import java.util.AbstractMap; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +import tools.refinery.viatra.runtime.matchers.util.Preconditions; + +/** + * Provides VIATRA Query with additional hints on how a query should be evaluated. The same hint can be provided to multiple queries. + * + *

    This class is immutable. Overriding options will create a new instance. + * + *

    + * Here be dragons: for advanced users only. + * + * @author Bergmann Gabor + * + */ +public class QueryEvaluationHint { + + /** + * @since 2.0 + * + */ + public enum BackendRequirement { + /** + * The current hint does not specify any backend requirement + */ + UNSPECIFIED, + /** + * The current hint specifies that the default search backend of the engine should be used + */ + DEFAULT_SEARCH, + /** + * The current hint specifies that the default caching backend of the engine should be used + */ + DEFAULT_CACHING, + /** + * The current hint specifies that a specific backend is to be used + */ + SPECIFIC + } + + final IQueryBackendFactory queryBackendFactory; + final Map, Object> backendHintSettings; + final BackendRequirement requirement; + + /** + * Specifies the suggested query backend requirements, and value settings for additional backend-specific options. + * + *

    + * The backend requirement type must not be {@link BackendRequirement#SPECIFIC} - for that case, use the constructor + * {@link #QueryEvaluationHint(Map, IQueryBackendFactory)}. + * + * @param backendHintSettings + * if non-null, each entry in the map overrides backend-specific options regarding query evaluation + * (null-valued map entries permitted to erase hints); passing null means default options associated with + * the query + * @param backendRequirementType + * defines the kind of backend requirement + * @since 2.0 + */ + public QueryEvaluationHint(Map, Object> backendHintSettings, BackendRequirement backendRequirementType) { + super(); + Preconditions.checkArgument(backendRequirementType != null, "Specific requirement needs to be set"); + Preconditions.checkArgument(backendRequirementType != BackendRequirement.SPECIFIC, "Specific backend requirement needs providing a corresponding backend type"); + this.queryBackendFactory = null; + this.requirement = backendRequirementType; + this.backendHintSettings = (backendHintSettings == null) + ? Collections., Object> emptyMap() + : new HashMap<>(backendHintSettings); + } + + /** + * Specifies the suggested query backend, and value settings for additional backend-specific options. The first + * parameter can be null; if the second parameter is null, it is expected that the other constructor is called + * instead with a {@link BackendRequirement#UNSPECIFIED} parameter. + * + * @param backendHintSettings + * if non-null, each entry in the map overrides backend-specific options regarding query evaluation + * (null-valued map entries permitted to erase hints); passing null means default options associated with + * the query + * @param queryBackendFactory + * overrides the query evaluator algorithm; passing null retains the default algorithm associated with + * the query + * @since 1.5 + */ + public QueryEvaluationHint( + Map, Object> backendHintSettings, + IQueryBackendFactory queryBackendFactory) { + super(); + this.queryBackendFactory = queryBackendFactory; + this.requirement = (queryBackendFactory == null) ? BackendRequirement.UNSPECIFIED : BackendRequirement.SPECIFIC; + this.backendHintSettings = (backendHintSettings == null) + ? Collections., Object> emptyMap() + : new HashMap<>(backendHintSettings); + } + + /** + * Returns the backend requirement described by this hint. If a specific backend is required, that can be queried by {@link #getQueryBackendFactory()}. + * @since 2.0 + */ + public BackendRequirement getQueryBackendRequirementType() { + return requirement; + } + + /** + * A suggestion for choosing the query evaluator algorithm. + * + *

    + * Returns null iff {@link #getQueryBackendRequirementType()} does not return {@link BackendRequirement#SPECIFIC}; + * in such cases a corresponding default backend is selected inside the engine + */ + public IQueryBackendFactory getQueryBackendFactory() { + return queryBackendFactory; + } + + /** + * Each entry in the immutable map overrides backend-specific options regarding query evaluation. + * + *

    The map is non-null, even if empty. + * Null-valued map entries are also permitted to erase hints via {@link #overrideBy(QueryEvaluationHint)}. + * + * @since 1.5 + */ + public Map, Object> getBackendHintSettings() { + return backendHintSettings; + } + + + /** + * Override values in this hint and return a consolidated instance. + * + * @since 1.4 + */ + public QueryEvaluationHint overrideBy(QueryEvaluationHint overridingHint){ + if (overridingHint == null) + return this; + + BackendRequirement overriddenRequirement = this.getQueryBackendRequirementType(); + if (overridingHint.getQueryBackendRequirementType() != BackendRequirement.UNSPECIFIED) { + overriddenRequirement = overridingHint.getQueryBackendRequirementType(); + } + Map, Object> hints = new HashMap<>(this.getBackendHintSettings()); + if (overridingHint.getBackendHintSettings() != null) { + hints.putAll(overridingHint.getBackendHintSettings()); + } + if (overriddenRequirement == BackendRequirement.SPECIFIC) { + IQueryBackendFactory factory = this.getQueryBackendFactory(); + if (overridingHint.getQueryBackendFactory() != null) { + factory = overridingHint.getQueryBackendFactory(); + } + return new QueryEvaluationHint(hints, factory); + } else { + return new QueryEvaluationHint(hints, overriddenRequirement); + } + } + + /** + * Returns whether the given hint option is overridden. + * @since 1.5 + */ + public boolean isOptionOverridden(QueryHintOption option) { + return getBackendHintSettings().containsKey(option); + } + + /** + * Returns the value of the given hint option from the given hint collection, or null if not defined. + * @since 1.5 + */ + @SuppressWarnings("unchecked") + public HintValue getValueOrNull(QueryHintOption option) { + return (HintValue) getBackendHintSettings().get(option); + } + + /** + * Returns the value of the given hint option from the given hint collection, or the default value if not defined. + * Intended to be called by backends to find out the definitive value that should be considered. + * @since 1.5 + */ + public HintValue getValueOrDefault(QueryHintOption option) { + return option.getValueOrDefault(this); + } + + @Override + public int hashCode() { + return Objects.hash(backendHintSettings, queryBackendFactory, requirement); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + QueryEvaluationHint other = (QueryEvaluationHint) obj; + return Objects.equals(backendHintSettings, other.backendHintSettings) + && + Objects.equals(queryBackendFactory, other.queryBackendFactory) + && + Objects.equals(requirement, other.requirement) + ; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + + if (getQueryBackendFactory() != null) + sb.append("backend: ").append(getQueryBackendFactory().getBackendClass().getSimpleName()); + if (! backendHintSettings.isEmpty()) { + sb.append("hints: "); + if(backendHintSettings instanceof AbstractMap){ + sb.append(backendHintSettings.toString()); + } else { + // we have to iterate on the contents + + String joinedHintMap = backendHintSettings.entrySet().stream() + .map(setting -> setting.getKey() + "=" + setting.getValue()).collect(Collectors.joining(", ")); + sb.append('{').append(joinedHintMap).append('}'); + } + } + + final String result = sb.toString(); + return result.isEmpty() ? "defaults" : result; + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/QueryHintOption.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/QueryHintOption.java new file mode 100644 index 00000000..2c6bb4de --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/QueryHintOption.java @@ -0,0 +1,122 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.backend; + +import java.util.Map; +import java.util.Objects; + +/** + * Each instance of this class corresponds to a given hint option. + * + * It is recommended to expose options to clients (and query backends) as public static fields. + * + * @author Gabor Bergmann + * @since 1.5 + */ +public class QueryHintOption { + + private String optionQualifiedName; + private HintValue defaultValue; + + /** + * Instantiates an option object with the given name and default value. + */ + public QueryHintOption(String optionQualifiedName, HintValue defaultValue) { + this.optionQualifiedName = optionQualifiedName; + this.defaultValue = defaultValue; + } + + /** + * This is the recommended constructor for hint options defined as static fields within an enclosing class. + * Combines the qualified name of the hint from the (qualified) name of the enclosing class and a local name (unique within that class). + */ + public QueryHintOption(Class optionNamespace, String optionLocalName, T defaultValue) { + this(String.format("%s@%s", optionLocalName, optionNamespace.getName()), defaultValue); + } + + /** + * Returns the qualified name, a unique string identifier of the option. + */ + public String getQualifiedName() { + return optionQualifiedName; + } + + /** + * Returns the default value of this hint option, which is to be used by query backends in the case no overriding value is assigned. + */ + public HintValue getDefaultValue() { + return defaultValue; + } + + /** + * Returns the value of this hint option from the given hint collection, or the default value if not defined. + * Intended to be called by backends to find out the definitive value that should be considered. + */ + @SuppressWarnings("unchecked") + public HintValue getValueOrDefault(QueryEvaluationHint hints) { + Object value = hints.getValueOrNull(this); + if (value == null) + return getDefaultValue(); + else { + return (HintValue) value; + } + } + + + /** + * Returns the value of this hint option from the given hint collection, or null if not defined. + */ + public HintValue getValueOrNull(QueryEvaluationHint hints) { + return hints.getValueOrNull(this); + } + + /** + * Returns whether this hint option is defined in the given hint collection. + */ + public boolean isOverriddenIn(QueryEvaluationHint hints) { + return hints.isOptionOverridden(this); + } + + /** + * Puts a value of this hint option into an option-to-value map. + * + *

    This method is offered in lieu of a builder API. + * Use this method on any number of hint options in order to populate an option-value map. + * Then instantiate the immutable {@link QueryEvaluationHint} using the map. + * + * @see #insertValueIfNondefault(Map, Object) + * @return the hint value that was previously present in the map under this hint option, carrying over the semantics of {@link Map#put(Object, Object)}. + */ + @SuppressWarnings("unchecked") + public HintValue insertOverridingValue(Map, Object> hints, HintValue overridingValue) { + return (HintValue) hints.put(this, overridingValue); + } + + /** + * Puts a value of this hint option into an option-to-value map, if the given value differs from the default value of the option. + * If the default value is provided instead, then the map is not updated. + * + *

    This method is offered in lieu of a builder API. + * Use this method on any number of hint options in order to populate an option-value map. + * Then instantiate the immutable {@link QueryEvaluationHint} using the map. + * + * @see #insertOverridingValue(Map, Object) + * @since 2.0 + */ + public void insertValueIfNondefault(Map, Object> hints, HintValue overridingValue) { + if (!Objects.equals(defaultValue, overridingValue)) + hints.put(this, overridingValue); + } + + @Override + public String toString() { + return optionQualifiedName; + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/ResultProviderRequestor.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/ResultProviderRequestor.java new file mode 100644 index 00000000..6ec6d53e --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/backend/ResultProviderRequestor.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.backend; + +import tools.refinery.viatra.runtime.matchers.context.IQueryResultProviderAccess; +import tools.refinery.viatra.runtime.matchers.psystem.IQueryReference; +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; + +/** + * Uniform way of requesting result providers for pattern calls within queries. + * Intended users are query backends, for calling other backends to deliver results of dependee queries. + * + * @author Gabor Bergmann + * @since 2.1 + */ +public class ResultProviderRequestor { + IQueryBackend callerBackend; + IQueryResultProviderAccess resultProviderAccess; + IQueryBackendHintProvider hintProvider; + ICallDelegationStrategy delegationStrategy; + QueryEvaluationHint callerHint; + QueryEvaluationHint universalOverride; + + + /** + * @param callerBackend the actual backend evaluating the calling pattern. + * @param resultProviderAccess + * @param hintProvider + * @param delegationStrategy + * @param callerHint a hint under which the calling pattern is evaluated, + * @param universalOverride if non-null, overrides the hint with extra options after the {@link ICallDelegationStrategy} + */ + public ResultProviderRequestor(IQueryBackend callerBackend, IQueryResultProviderAccess resultProviderAccess, + IQueryBackendHintProvider hintProvider, ICallDelegationStrategy delegationStrategy, + QueryEvaluationHint callerHint, QueryEvaluationHint universalOverride) { + super(); + this.callerBackend = callerBackend; + this.resultProviderAccess = resultProviderAccess; + this.hintProvider = hintProvider; + this.delegationStrategy = delegationStrategy; + this.callerHint = callerHint; + this.universalOverride = universalOverride; + } + + + + + /** + * + * @param call a {@link PConstraint} in a query that calls another query. + * @param spotOverride if non-null, overrides the hint with extra options after the {@link ICallDelegationStrategy} + * and the universal override specified in the constructor + * @return the obtained result provider + */ + public IQueryResultProvider requestResultProvider(IQueryReference call, QueryEvaluationHint spotOverride) { + QueryEvaluationHint hints = + delegationStrategy.transformHints(call, callerHint, callerBackend, hintProvider); + + if (universalOverride != null) + hints = hints.overrideBy(universalOverride); + + if (spotOverride != null) + hints = hints.overrideBy(spotOverride); + + return resultProviderAccess.getResultProvider(call.getReferredQuery(), hints); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/AbstractQueryMetaContext.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/AbstractQueryMetaContext.java new file mode 100644 index 00000000..99611758 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/AbstractQueryMetaContext.java @@ -0,0 +1,69 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Grill Balázs, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.context; + +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * Common abstract class for implementers of {@link IQueryMetaContext} + * + * @author Grill Balázs + * @since 1.3 + * + */ +public abstract class AbstractQueryMetaContext implements IQueryMetaContext { + + /** + * @since 2.0 + */ + @Override + public Map> getConditionalImplications(IInputKey implyingKey) { + return new HashMap<>(); + } + + /** + * @since 1.6 + */ + @Override + public boolean canLeadOutOfScope(IInputKey key) { + return key.getArity() > 1; + } + + /** + * @since 1.6 + */ + @Override + public Comparator getSuggestedEliminationOrdering() { + return (o1, o2) -> 0; + } + + /** + * @since 1.6 + */ + @Override + public Collection getWeakenedAlternatives(IInputKey implyingKey) { + return Collections.emptySet(); + } + + @Override + public boolean isPosetKey(IInputKey key) { + return false; + } + + @Override + public IPosetComparator getPosetComparator(Iterable key) { + return null; + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/AbstractQueryRuntimeContext.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/AbstractQueryRuntimeContext.java new file mode 100644 index 00000000..c797eff9 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/AbstractQueryRuntimeContext.java @@ -0,0 +1,21 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Grill Balázs, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.context; + +/** + * This class is intended to be extended by implementors. The main purpose of this abstract implementation to protect + * implementors from future changes in the interface. + * + * @author Grill Balázs + * @since 1.4 + * + */ +public abstract class AbstractQueryRuntimeContext implements IQueryRuntimeContext { + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/IInputKey.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/IInputKey.java new file mode 100644 index 00000000..4dbcca88 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/IInputKey.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.context; + +/** + * An input key identifies an input (extensional) relation, such as the instance set of a given node or edge type, or the direct containment relation. + * + *

    The input key, at the very minimum, is associated with an arity (number of columns), a user-friendly name, and a string identifier (for distributive purposes). + * + *

    The input key itself must be an immutable data object that properly overrides equals() and hashCode(). + * It must be instantiable without using the query context object, so that query specifications may construct the appropriate PQueries. + * + * @author Bergmann Gabor + * + */ +public interface IInputKey { + + /** + * A user-friendly name that can be shown on screen for debug purposes, included in exceptions, etc. + */ + public String getPrettyPrintableName(); + /** + * An internal string identifier that can be used to uniquely identify to input key (relevant for distributed applications). + */ + public String getStringID(); + + /** + * The width of tuples in this relation. + */ + public int getArity(); + + /** + * Returns true iff instance tuples of the key can be enumerated. + *

    If false, the runtime can only test tuple membership in the extensional relation identified by the key, but not enumerate member tuples in general. + */ + boolean isEnumerable(); + + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/IPosetComparator.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/IPosetComparator.java new file mode 100644 index 00000000..e2d5bcee --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/IPosetComparator.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.context; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; + +/** + * Implementations of this interface aid the query engine with the ordering of poset elements. This information is + * particularly important in the delete and re-derive evaluation mode because they let the engine identify monotone + * change pairs. + * + * @author Tamas Szabo + * @since 1.6 + */ +public interface IPosetComparator { + + /** + * Returns true if the 'left' tuple of poset elements is smaller or equal than the 'right' tuple of poset elements according to + * the partial order that this poset comparator employs. + * + * @param left + * the left tuple of poset elements + * @param right + * the right tuple of poset elements + * @return true if left is smaller or equal to right, false otherwise + */ + public boolean isLessOrEqual(final Tuple left, final Tuple right); + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryBackendContext.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryBackendContext.java new file mode 100644 index 00000000..04f00aaa --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryBackendContext.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Grill Balázs, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.context; + +import org.apache.log4j.Logger; +import tools.refinery.viatra.runtime.matchers.backend.IMatcherCapability; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendHintProvider; +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.psystem.analysis.QueryAnalyzer; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; + +/** + * This interface is a collector which holds every API that is provided by the engine to control + * the operation of the backends. + * + * @since 1.5 + * @noimplement This interface is not intended to be implemented by clients. + */ +public interface IQueryBackendContext { + + Logger getLogger(); + + IQueryRuntimeContext getRuntimeContext(); + + IQueryCacheContext getQueryCacheContext(); + + IQueryBackendHintProvider getHintProvider(); + + IQueryResultProviderAccess getResultProviderAccess(); + + QueryAnalyzer getQueryAnalyzer(); + + /** + * @since 2.0 + */ + IMatcherCapability getRequiredMatcherCapability(PQuery query, QueryEvaluationHint overrideHints); + + /** + * @since 1.6 + */ + boolean areUpdatesDelayed(); + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryCacheContext.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryCacheContext.java new file mode 100644 index 00000000..617207f6 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryCacheContext.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.context; + +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackend; +import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; + +/** + * Provides information on already cached queries to query evaluator backends at runtime. + * + * @author Bergmann Gabor + * + */ +public interface IQueryCacheContext { + + /** + * Checks if there already is a caching result provider for the given query. + *

    Returns false if called while the caching result provider of the given query is being constructed in the first place. + */ + public boolean isResultCached(PQuery query); + + /** + * Returns a caching result provider for the given query; it must be constructed if it does not exist yet. + *

    Caution: behavior undefined if called while the caching result provider of the given query is being constructed. + * Beware of infinite loops. + *

    Postcondition: {@link IQueryBackend#isCaching()} returns true for the {@link #getQueryBackend()} of the returned provider + * + * @throws ViatraQueryRuntimeException + */ + public IQueryResultProvider getCachingResultProvider(PQuery query); +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryMetaContext.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryMetaContext.java new file mode 100644 index 00000000..4c22a3cb --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryMetaContext.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.context; + +import java.util.Collection; +import java.util.Comparator; +import java.util.Map; +import java.util.Set; + +/** + * Provides metamodel information (relationship of input keys) to query evaluator backends at runtime and at query planning time. + * + * @noimplement Implementors should extend {@link AbstractQueryMetaContext} instead of directly implementing this interface. + * @author Bergmann Gabor + */ +public interface IQueryMetaContext { + + /** + * Returns true iff instance tuples of the given key can be enumerated. + *

    If false, the runtime can only test tuple membership in the extensional relation identified by the key, but not enumerate member tuples in general. + *

    Equivalent to {@link IInputKey#isEnumerable()}. + */ + boolean isEnumerable(IInputKey key); + + /** + * Returns true iff the set of instance tuples of the given key is immutable. + *

    If false, the runtime provides notifications upon change. + */ + boolean isStateless(IInputKey key); + + /** + * Returns a set of implications (weakened alternatives), + * with a suggestion for the query planner that satisfying them first may help in satisfying the implying key. + *

    Note that for the obvious reasons, enumerable keys can only be implied by enumerable keys. + *

    Must follow directly or transitively from implications of {@link #getImplications(IInputKey)}. + * @since 1.6 + */ + Collection getWeakenedAlternatives(IInputKey implyingKey); + + /** + * Returns known direct implications, e.g. edge supertypes, edge opposites, node type constraints, etc. + *

    Note that for the obvious reasons, enumerable keys can only be implied by enumerable keys. + */ + Collection getImplications(IInputKey implyingKey); + + /** + * Returns known "double dispatch" implications, where the given implying key implies other input keys under certain additional conditions (themselves input keys). + * For example, a "type x, unscoped" input key may imply the "type x, in scope" input key under the condition of the input key "x is in scope" + * + *

    Note that for the obvious reasons, enumerable keys can only be implied by enumerable keys (either as the implying key or as the additional condition). + *

    Note that symmetry is not required, i.e. the additional conditions do not have to list the same conditional implication. + * @return multi-map, where the keys are additional conditions and the values are input key implications jointly implied by the condition and the given implying key. + * @since 2.0 + */ + Map> getConditionalImplications(IInputKey implyingKey); + + /** + * Returns functional dependencies of the input key expressed in terms of column indices. + * + *

    Each entry of the map is a functional dependency rule, where the entry key specifies source columns and the entry value specifies target columns. + */ + Map, Set> getFunctionalDependencies(IInputKey key); + + /** + * For query normalizing, this is the order suggested for trying to eliminate input keys. + * @since 1.6 + */ + Comparator getSuggestedEliminationOrdering(); + + /** + * Tells whether the given {@link IInputKey} is an edge and may lead out of scope. + * + * @since 1.6 + */ + boolean canLeadOutOfScope(IInputKey key); + + /** + * Returns true if the given {@link IInputKey} represents a poset type. + * @since 1.6 + */ + boolean isPosetKey(IInputKey key); + + /** + * Returns an {@link IPosetComparator} for the given set of {@link IInputKey}s. + * + * @param keys an iterable collection of input keys + * @return the poset comparator + * @since 1.6 + */ + IPosetComparator getPosetComparator(Iterable keys); + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryResultProviderAccess.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryResultProviderAccess.java new file mode 100644 index 00000000..7fecd01a --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryResultProviderAccess.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Grill Balázs, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.context; + +import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider; +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; + +/** + * This interface exposes API to request {@link IQueryResultProvider} for {@link PQuery} instances. + * + * @author Grill Balázs + * @since 1.5 + * @noimplement This interface is not intended to be implemented by clients. + */ +public interface IQueryResultProviderAccess { + + /** + * Get a result provider for the given {@link PQuery}, which conforms the capabilities requested by the + * given {@link QueryEvaluationHint} object. + * @throws ViatraQueryRuntimeException + */ + public IQueryResultProvider getResultProvider(PQuery query, QueryEvaluationHint overrideHints); + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryRuntimeContext.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryRuntimeContext.java new file mode 100644 index 00000000..c2e90614 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryRuntimeContext.java @@ -0,0 +1,281 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.context; + +import java.lang.reflect.InvocationTargetException; +import java.util.Optional; +import java.util.concurrent.Callable; + +import tools.refinery.viatra.runtime.matchers.planning.helpers.StatisticsHelper; +import tools.refinery.viatra.runtime.matchers.tuple.ITuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.Accuracy; + +/** + * Provides instance model information (relations corresponding to input keys) to query evaluator backends at runtime. + * Implementors shall extend {@link AbstractQueryRuntimeContext} instead directly this interface. + * + * @author Bergmann Gabor + * @noimplement This interface is not intended to be implemented by clients. Extend {@link AbstractQueryRuntimeContext} instead. + */ +public interface IQueryRuntimeContext { + /** + * Provides metamodel-specific info independent of the runtime instance model. + */ + public IQueryMetaContext getMetaContext(); + + + /** + * The given callable will be executed, and all model traversals will be delayed until the execution is done. If + * there are any outstanding information to be read from the model, a single coalesced model traversal will + * initialize the caches and deliver the notifications. + * + *

    Calls may be nested. A single coalesced traversal will happen at the end of the outermost call. + * + *

    Caution: results returned by the runtime context may be incomplete during the coalescing period, to be corrected by notifications sent during the final coalesced traversal. + * For example, if a certain input key is not cached yet, an empty relation may be reported during callable.call(); the cache will be constructed after the call terminates and notifications will deliver the entire content of the relation. + * Non-incremental query backends should therefore never enumerate input keys while coalesced (verify using {@link #isCoalescing()}). + * + * @param callable + */ + public abstract V coalesceTraversals(Callable callable) throws InvocationTargetException; + /** + * @return true iff currently within a coalescing section (i.e. within the callable of a call to {@link #coalesceTraversals(Callable)}). + */ + public boolean isCoalescing(); + + /** + * Returns true if index is available for the given key providing the given service. + * @throws IllegalArgumentException if key is not enumerable or an unknown type, see {@link IQueryMetaContext#isEnumerable(IInputKey)}. + * @since 1.4 + */ + public boolean isIndexed(IInputKey key, IndexingService service); + + /** + * If the given (enumerable) input key is not yet indexed, the model will be traversed + * (after the end of the outermost coalescing block, see {@link IQueryRuntimeContext#coalesceTraversals(Callable)}) + * so that the index can be built. It is possible that the base indexer will select a higher indexing level merging + * multiple indexing requests to an appropriate level. + * + *

    Postcondition: After invoking this method, {@link #getIndexed(IInputKey, IndexingService)} for the same key + * and service will be guaranteed to return the requested or a highing indexing level as soon as {@link #isCoalescing()} first returns false. + * + *

    Precondition: the given key is enumerable, see {@link IQueryMetaContext#isEnumerable(IInputKey)}. + * @throws IllegalArgumentException if key is not enumerable or an unknown type, see {@link IQueryMetaContext#isEnumerable(IInputKey)}. + * @since 1.4 + */ + public void ensureIndexed(IInputKey key, IndexingService service); + + /** + * Returns the number of tuples in the extensional relation identified by the input key seeded with the given mask and tuple. + * + * @param key an input key + * @param seedMask + * a mask that extracts those parameters of the input key (from the entire parameter list) that should be + * bound to a fixed value; must not be null. Note: any given index must occur at most once in seedMask. + * @param seed + * the tuple of fixed values restricting the match set to be considered, in the same order as given in + * parameterSeedMask, so that for each considered match tuple, + * projectedParameterSeed.equals(parameterSeedMask.transform(match)) should hold. Must not be null. + * + * @return the number of tuples in the model for the given key and seed + * + *

    Precondition: the given key is enumerable, see {@link IQueryMetaContext#isEnumerable(IInputKey)}. + * @throws IllegalArgumentException if key is not enumerable, see {@link IQueryMetaContext#isEnumerable(IInputKey)}. + * @since 1.7 + */ + public int countTuples(IInputKey key, TupleMask seedMask, ITuple seed); + + + /** + * Gives an estimate of the number of different groups the tuples of the given relation are projected into by the given mask + * (e.g. for an identity mask, this means the full relation size). The estimate must meet the required accuracy. + * + *

    Must accept any input key, even non-enumerables or those not recognized by this runtime context. + * If there is insufficient information to provide an answer up to the required precision, {@link Optional#empty()} is returned. + * + *

    PRE: {@link TupleMask#isNonrepeating()} must hold for the group mask. + * + * @return if available, an estimate of the cardinality of the projection of the given extensional relation, with the desired accuracy. + * + * @since 2.1 + */ + public Optional estimateCardinality(IInputKey key, TupleMask groupMask, Accuracy requiredAccuracy); + + + /** + * Gives an estimate of the average size of different groups the tuples of the given relation are projected into by the given mask + * (e.g. for an identity mask, this means 1, while for an empty mask, the result is the full relation size). + * The estimate must meet the required accuracy. + * + *

    Must accept any input key, even non-enumerables or those not recognized by this runtime context. + * If there is insufficient information to provide an answer up to the required precision, {@link Optional#empty()} may be returned. + * + *

    For an empty relation, zero is acceptable as an exact answer. + * + *

    PRE: {@link TupleMask#isNonrepeating()} must hold for the group mask. + * + * @return if available, an estimate of the average size of each projection group of the given extensional relation, with the desired accuracy. + * + * @since 2.1 + */ + public default Optional estimateAverageBucketSize(IInputKey key, TupleMask groupMask, Accuracy requiredAccuracy) { + if (key.isEnumerable()) { + return StatisticsHelper.estimateAverageBucketSize(groupMask, requiredAccuracy, + (mask, accuracy) -> this.estimateCardinality(key, mask, accuracy)); + } else return groupMask.isIdentity() ? Optional.of(1.0) : Optional.empty(); + } + + + /** + * Returns the tuples in the extensional relation identified by the input key, optionally seeded with the given tuple. + * + * @param key an input key + * @param seedMask + * a mask that extracts those parameters of the input key (from the entire parameter list) that should be + * bound to a fixed value; must not be null. Note: any given index must occur at most once in seedMask. + * @param seed + * the tuple of fixed values restricting the match set to be considered, in the same order as given in + * parameterSeedMask, so that for each considered match tuple, + * projectedParameterSeed.equals(parameterSeedMask.transform(match)) should hold. Must not be null. + * @return the tuples in the model for the given key and seed + * + *

    Precondition: the given key is enumerable, see {@link IQueryMetaContext#isEnumerable(IInputKey)}. + * @throws IllegalArgumentException if key is not enumerable, see {@link IQueryMetaContext#isEnumerable(IInputKey)}. + * @since 1.7 + */ + public Iterable enumerateTuples(IInputKey key, TupleMask seedMask, ITuple seed); + + /** + * Simpler form of {@link #enumerateTuples(IInputKey, TupleMask, Tuple)} in the case where all values of the tuples + * are bound by the seed except for one. + * + *

    + * Selects the tuples in the extensional relation identified by the input key, optionally seeded with the given + * tuple, and then returns the single value from each tuple which is not bound by the ssed mask. + * + * @param key + * an input key + * @param seedMask + * a mask that extracts those parameters of the input key (from the entire parameter list) that should be + * bound to a fixed value; must not be null. Note: any given index must occur at most + * once in seedMask, and seedMask must include all parameters in any arbitrary order except one. + * @param seed + * the tuple of fixed values restricting the match set to be considered, in the same order as given in + * parameterSeedMask, so that for each considered match tuple, + * projectedParameterSeed.equals(parameterSeedMask.transform(match)) should hold. Must not be null. + * @return the objects in the model for the given key and seed + * + *

    + * Precondition: the given key is enumerable, see {@link IQueryMetaContext#isEnumerable(IInputKey)}. + * @throws IllegalArgumentException + * if key is not enumerable, see {@link IQueryMetaContext#isEnumerable(IInputKey)}. + * @since 1.7 + */ + public Iterable enumerateValues(IInputKey key, TupleMask seedMask, ITuple seed); + + /** + * Simpler form of {@link #enumerateTuples(IInputKey, TupleMask, Tuple)} in the case where all values of the tuples + * are bound by the seed. + * + *

    + * Returns whether the given tuple is in the extensional relation identified by the input key. + * + *

    + * Note: this call works for non-enumerable input keys as well. + * + * @param key + * an input key + * @param seed + * the tuple of fixed values restricting the match set to be considered, in the same order as given in + * parameterSeedMask, so that for each considered match tuple, + * projectedParameterSeed.equals(parameterSeedMask.transform(match)) should hold. Must not be null. + * @return true iff there is at least a single tuple contained in the relation that corresponds to the seed tuple + * @since 2.0 + */ + public boolean containsTuple(IInputKey key, ITuple seed); + + + /** + * Subscribes for updates in the extensional relation identified by the input key, optionally seeded with the given tuple. + *

    This should be called after invoking + * + * @param key an input key + * @param seed can be null or a tuple with matching arity; + * if non-null, only those updates in the model are notified about + * that match the seed at positions where the seed is non-null. + * @param listener will be notified of future changes + * + *

    Precondition: the given key is enumerable, see {@link IQueryMetaContext#isEnumerable(IInputKey)}. + * @throws IllegalArgumentException if key is not enumerable, see {@link IQueryMetaContext#isEnumerable(IInputKey)}. + */ + public void addUpdateListener(IInputKey key, Tuple seed, IQueryRuntimeContextListener listener); + + /** + * Unsubscribes from updates in the extensional relation identified by the input key, optionally seeded with the given tuple. + * + * @param key an input key + * @param seed can be null or a tuple with matching arity; + * if non-null, only those updates in the model are notified about + * that match the seed at positions where the seed is non-null. + * @param listener will no longer be notified of future changes + * + *

    Precondition: the given key is enumerable, see {@link IQueryMetaContext#isEnumerable(IInputKey)}. + * @throws IllegalArgumentException if key is not enumerable, see {@link IQueryMetaContext#isEnumerable(IInputKey)}. + */ + public void removeUpdateListener(IInputKey key, Tuple seed, IQueryRuntimeContextListener listener); + /* + TODO: uniqueness + */ + + /** + * Wraps the external element into the internal representation that is to be used by the query backend + *

    model element -> internal object. + *

    null must be mapped to null. + */ + public Object wrapElement(Object externalElement); + + /** + * Unwraps the internal representation of the element into its original form + *

    internal object -> model element + *

    null must be mapped to null. + */ + public Object unwrapElement(Object internalElement); + + /** + * Unwraps the tuple of elements into the internal representation that is to be used by the query backend + *

    model elements -> internal objects + *

    null must be mapped to null. + */ + public Tuple wrapTuple(Tuple externalElements); + + /** + * Unwraps the tuple of internal representations of elements into their original forms + *

    internal objects -> model elements + *

    null must be mapped to null. + */ + public Tuple unwrapTuple(Tuple internalElements); + + /** + * Starts wildcard indexing for the given service. After this call, no registration is required for this {@link IndexingService}. + * a previously set wildcard level cannot be lowered, only extended. + * @since 1.4 + */ + public void ensureWildcardIndexing(IndexingService service); + + /** + * Execute the given runnable after traversal. It is guaranteed that the runnable is executed as soon as + * the indexing is finished. The callback is executed only once, then is removed from the callback queue. + * @param traversalCallback + * @throws InvocationTargetException + * @since 1.4 + */ + public void executeAfterTraversal(Runnable runnable) throws InvocationTargetException; +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryRuntimeContextListener.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryRuntimeContextListener.java new file mode 100644 index 00000000..7be27d56 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/IQueryRuntimeContextListener.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.context; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; + +/** + * Listens for changes in the runtime context. + * @author Bergmann Gabor + * + */ +public interface IQueryRuntimeContextListener { + + /** + * The given tuple was inserted into or removed from the input relation indicated by the given key. + * @param key the key identifying the input relation that was updated + * @param updateTuple the tuple that was inserted or removed + * @param isInsertion true if it was an insertion, false otherwise. + */ + public void update(IInputKey key, Tuple updateTuple, boolean isInsertion); +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/IndexingService.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/IndexingService.java new file mode 100644 index 00000000..8210765d --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/IndexingService.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Grill Balázs, IncQueryLabs + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.context; + +/** + * These are the different services which can be provided by an {@link IQueryRuntimeContext} implementation. + * + * @author Grill Balázs + * @since 1.4 + * + */ +public enum IndexingService { + + /** + * Cardinality information is available. Makes possible to calculate + * unseeded calls of {@link IQueryRuntimeContext#countTuples(IInputKey, tools.refinery.viatra.runtime.matchers.tuple.Tuple)} + */ + STATISTICS, + + /** + * The indexer can provide notifications about changes in the model. + */ + NOTIFICATIONS, + + /** + * Enables enumeration of instances and reverse-navigation. + */ + INSTANCES + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/InputKeyImplication.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/InputKeyImplication.java new file mode 100644 index 00000000..2a403810 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/InputKeyImplication.java @@ -0,0 +1,103 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.context; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Data object representing the implication of an input key, in use cases including edge supertypes, edge opposites, node type constraints, etc. + * + *

    Each instance tuple of the implying input key (if given) implies the presence of an instance tuple of the implied input key consisting of elements of the original tuple at given positions. + * When the input key is null, it is not an input constraint but some other source that implies input keys. + * + *

    The implication is an immutable data object. + * + * @author Bergmann Gabor + * + */ +public final class InputKeyImplication { + private IInputKey implyingKey; + private IInputKey impliedKey; + private List impliedIndices; + + /** + * Optional. Instance tuples of this input key imply an instance tuple of another key. + * Sometimes it is not an input key that implies other input keys, so this attribute can be null. + */ + public IInputKey getImplyingKey() { + return implyingKey; + } + /** + * An instance tuple of this input key is implied by another key. + */ + public IInputKey getImpliedKey() { + return impliedKey; + } + /** + * The implied instance tuple consists of the values in the implying tuple at these indices. + */ + public List getImpliedIndices() { + return impliedIndices; + } + /** + * @param implyingKey instance tuples of this input key imply an instance tuple of the other key. + * @param impliedKey instance tuple of this input key is implied by the other key. + * @param implyingIndices the implied instance tuple consists of the values in the implying tuple at these indices. + */ + public InputKeyImplication(IInputKey implyingKey, IInputKey impliedKey, + List implyingIndices) { + super(); + this.implyingKey = implyingKey; + this.impliedKey = impliedKey; + this.impliedIndices = Collections.unmodifiableList(new ArrayList<>(implyingIndices)); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + + ((impliedIndices == null) ? 0 : impliedIndices.hashCode()); + result = prime * result + + ((impliedKey == null) ? 0 : impliedKey.hashCode()); + result = prime * result + + ((implyingKey == null) ? 0 : implyingKey.hashCode()); + return result; + } + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof InputKeyImplication)) + return false; + InputKeyImplication other = (InputKeyImplication) obj; + if (impliedIndices == null) { + if (other.impliedIndices != null) + return false; + } else if (!impliedIndices.equals(other.impliedIndices)) + return false; + if (impliedKey == null) { + if (other.impliedKey != null) + return false; + } else if (!impliedKey.equals(other.impliedKey)) + return false; + if (implyingKey == null) { + if (other.implyingKey != null) + return false; + } else if (!implyingKey.equals(other.implyingKey)) + return false; + return true; + } + + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/common/BaseInputKeyWrapper.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/common/BaseInputKeyWrapper.java new file mode 100644 index 00000000..f9b05e5b --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/common/BaseInputKeyWrapper.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.context.common; + +import tools.refinery.viatra.runtime.matchers.context.IInputKey; + + +/** + * An input key that is identified by a single wrapped object and the class of the wrapper. + * @author Bergmann Gabor + * + */ +public abstract class BaseInputKeyWrapper implements IInputKey { + protected Wrapped wrappedKey; + + public BaseInputKeyWrapper(Wrapped wrappedKey) { + super(); + this.wrappedKey = wrappedKey; + } + + public Wrapped getWrappedKey() { + return wrappedKey; + } + + + @Override + public int hashCode() { + return ((wrappedKey == null) ? 0 : wrappedKey.hashCode()); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(this.getClass().equals(obj.getClass()))) + return false; + BaseInputKeyWrapper other = (BaseInputKeyWrapper) obj; + if (wrappedKey == null) { + if (other.wrappedKey != null) + return false; + } else if (!wrappedKey.equals(other.wrappedKey)) + return false; + return true; + } + + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/common/JavaTransitiveInstancesKey.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/common/JavaTransitiveInstancesKey.java new file mode 100644 index 00000000..eb972c2d --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/common/JavaTransitiveInstancesKey.java @@ -0,0 +1,165 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.context.common; + + + +/** + * Instance tuples are of form (x), where object x is an instance of the given Java class or its subclasses. + *

    Fine print 1: classes with the same name are considered equivalent. + * Can be instantiated with class name, even if the class itself is not loaded yet; but if the class is available, passing it in the constructor is beneficial to avoid classloading troubles. + *

    Fine print 2: primitive types (char, etc.) are transparently treated as their wrapper class (Character, etc.). + *

    Non-enumerable type, can only be checked. + *

    Stateless type (objects can't change their type) + * @author Bergmann Gabor + * +*/ +public class JavaTransitiveInstancesKey extends BaseInputKeyWrapper { + + /** + * The actual Class whose (transitive) instances this relation contains. Can be null at compile time, if only the name is available. + * Can be a primitive. + */ + private Class cachedOriginalInstanceClass; + + /** + * Same as {@link #cachedOriginalInstanceClass}, but primitive classes are replaced with their wrapper classes (e.g. int --> java.lang.Integer). + */ + private Class cachedWrapperInstanceClass; + + /** + * Preferred constructor. + */ + public JavaTransitiveInstancesKey(Class instanceClass) { + this(primitiveTypeToWrapperClass(instanceClass).getName()); + this.cachedOriginalInstanceClass = instanceClass; + } + + /** + * Call this constructor only in contexts where the class itself is not available for loading, e.g. it has not yet been compiled. + */ + public JavaTransitiveInstancesKey(String className) { + super(className); + } + + + /** + * Returns null if class cannot be loaded. + */ + private Class getOriginalInstanceClass() { + if (cachedOriginalInstanceClass == null) { + try { + resolveClassInternal(); + } catch (ClassNotFoundException e) { + // class not yet available at this point + } + } + return cachedOriginalInstanceClass; + } + + + /** + * @return non-null instance class + * @throws ClassNotFoundException + */ + private Class forceGetOriginalInstanceClass() throws ClassNotFoundException { + if (cachedOriginalInstanceClass == null) { + resolveClassInternal(); + } + return cachedOriginalInstanceClass; + } + + /** + * @return non-null instance class, wrapped if primitive class + * @throws ClassNotFoundException + */ + public Class forceGetWrapperInstanceClass() throws ClassNotFoundException { + forceGetOriginalInstanceClass(); + return getWrapperInstanceClass(); + } + /** + * @return non-null instance class, wrapped if primitive class + * @throws ClassNotFoundException + */ + public Class forceGetInstanceClass() throws ClassNotFoundException { + return forceGetWrapperInstanceClass(); + } + + /** + * @return instance class, wrapped if primitive class, null if class cannot be loaded + */ + public Class getWrapperInstanceClass() { + if (cachedWrapperInstanceClass == null) { + cachedWrapperInstanceClass = primitiveTypeToWrapperClass(getOriginalInstanceClass()); + } + return cachedWrapperInstanceClass; + } + /** + * @return instance class, wrapped if primitive class, null if class cannot be loaded + */ + public Class getInstanceClass() { + return getWrapperInstanceClass(); + } + + private void resolveClassInternal() throws ClassNotFoundException { + cachedOriginalInstanceClass = Class.forName(wrappedKey); + } + + @Override + public String getPrettyPrintableName() { + getWrapperInstanceClass(); + return cachedWrapperInstanceClass == null ? wrappedKey == null ? "" : wrappedKey : cachedWrapperInstanceClass.getName(); + } + + @Override + public String getStringID() { + return "javaClass#"+ getPrettyPrintableName(); + } + + @Override + public int getArity() { + return 1; + } + + @Override + public boolean isEnumerable() { + return false; + } + + @Override + public String toString() { + return this.getPrettyPrintableName(); + } + + private static Class primitiveTypeToWrapperClass(Class instanceClass) { + if (instanceClass != null && instanceClass.isPrimitive()) { + if (Void.TYPE.equals(instanceClass)) + return Void.class; + if (Boolean.TYPE.equals(instanceClass)) + return Boolean.class; + if (Character.TYPE.equals(instanceClass)) + return Character.class; + if (Byte.TYPE.equals(instanceClass)) + return Byte.class; + if (Short.TYPE.equals(instanceClass)) + return Short.class; + if (Integer.TYPE.equals(instanceClass)) + return Integer.class; + if (Long.TYPE.equals(instanceClass)) + return Long.class; + if (Float.TYPE.equals(instanceClass)) + return Float.class; + if (Double.TYPE.equals(instanceClass)) + return Double.class; + } + return instanceClass; + } + + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/surrogate/SurrogateQueryRegistry.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/surrogate/SurrogateQueryRegistry.java new file mode 100644 index 00000000..bb24ec8c --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/context/surrogate/SurrogateQueryRegistry.java @@ -0,0 +1,153 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Abel Hegedus, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.context.surrogate; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.util.IProvider; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; +import tools.refinery.viatra.runtime.matchers.util.SingletonInstanceProvider; + +/** + * @author Abel Hegedus + * + */ +public class SurrogateQueryRegistry { + + private Map> registeredSurrogateQueryMap = new HashMap<>(); + private Map> dynamicSurrogateQueryMap = new HashMap<>(); + + /** + * Hidden constructor + */ + private SurrogateQueryRegistry() { + } + + private static final SurrogateQueryRegistry INSTANCE = new SurrogateQueryRegistry(); + + public static SurrogateQueryRegistry instance() { + return INSTANCE; + } + + /** + * + * @param feature + * @param surrogateQuery + * @return the previous surrogate query associated with feature, or null if there was no such query FQN registered + * @throws IllegalArgumentException if feature or surrogateQuery is null + */ + public IProvider registerSurrogateQueryForFeature(IInputKey feature, PQuery surrogateQuery) { + Preconditions.checkArgument(surrogateQuery != null, "Surrogate query must not be null!"); + return registerSurrogateQueryForFeature(feature, new SingletonInstanceProvider(surrogateQuery)); + } + + /** + * + * @param feature + * @param surrogateQuery + * @return the previous surrogate query associated with feature, or null + * if there was no such query registered + * @throws IllegalArgumentException + * if feature or surrogateQuery is null + */ + public IProvider registerSurrogateQueryForFeature(IInputKey feature, IProvider surrogateQueryProvider) { + Preconditions.checkArgument(feature != null, "Feature must not be null!"); + Preconditions.checkArgument(surrogateQueryProvider != null, "Surrogate query must not be null!"); + return registeredSurrogateQueryMap.put(feature, surrogateQueryProvider); + } + + public IProvider addDynamicSurrogateQueryForFeature(IInputKey feature, PQuery surrogateQuery) { + Preconditions.checkArgument(surrogateQuery != null, "Surrogate query FQN must not be null!"); + return addDynamicSurrogateQueryForFeature(feature, new SingletonInstanceProvider(surrogateQuery)); + } + + public IProvider addDynamicSurrogateQueryForFeature(IInputKey feature, IProvider surrogateQuery) { + Preconditions.checkArgument(feature != null, "Feature must not be null!"); + Preconditions.checkArgument(surrogateQuery != null, "Surrogate query FQN must not be null!"); + return dynamicSurrogateQueryMap.put(feature, surrogateQuery); + } + + public IProvider removeDynamicSurrogateQueryForFeature(IInputKey feature) { + Preconditions.checkArgument(feature != null, "Feature must not be null!"); + return dynamicSurrogateQueryMap.remove(feature); + } + + /** + * + * @param feature that may have surrogate query defined, null not allowed + * @return true if the feature has a surrogate query defined + * @throws IllegalArgumentException if feature is null + */ + public boolean hasSurrogateQueryFQN(IInputKey feature) { + Preconditions.checkArgument(feature != null, "Feature must not be null!"); + boolean surrogateExists = dynamicSurrogateQueryMap.containsKey(feature); + if(!surrogateExists){ + surrogateExists = registeredSurrogateQueryMap.containsKey(feature); + } + return surrogateExists; + } + + /** + * + * @param feature for which the surrogate query FQN should be returned + * @return the surrogate query FQN defined for the feature + * @throws IllegalArgumentException if feature is null + * @throws NoSuchElementException if the feature has no surrogate query defined, use {@link #hasSurrogateQueryFQN} to check + */ + public PQuery getSurrogateQuery(IInputKey feature) { + Preconditions.checkArgument(feature != null, "Feature must not be null!"); + IProvider surrogate = dynamicSurrogateQueryMap.get(feature); + if(surrogate == null) { + surrogate = registeredSurrogateQueryMap.get(feature); + } + if(surrogate != null) { + return surrogate.get(); + } else { + throw new NoSuchElementException(String.format("Feature %s has no surrogate query defined! Use #hasSurrogateQueryFQN to check existence.", feature)); + } + } + + /** + * @return an unmodifiable set of features with registered surrogate queries + */ + public Set getRegisteredSurrogateQueries() { + return Collections.unmodifiableSet(getRegisteredSurrogateQueriesInternal()); + } + + private Set getRegisteredSurrogateQueriesInternal() { + return registeredSurrogateQueryMap.keySet(); + } + + /** + * @return an unmodifiable set of features with dynamically added surrogate queries + */ + public Set getDynamicSurrogateQueries() { + return Collections.unmodifiableSet(getDynamicSurrogateQueriesInternal()); + } + + private Set getDynamicSurrogateQueriesInternal() { + return dynamicSurrogateQueryMap.keySet(); + } + + /** + * @return an unmodifiable set that contains all features with surrogate queries. + */ + public Set getAllSurrogateQueries() { + Set results = new HashSet<>(getRegisteredSurrogateQueriesInternal()); + results.addAll(getDynamicSurrogateQueriesInternal()); + return results; + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/AbstractTrivialMaskedMemory.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/AbstractTrivialMaskedMemory.java new file mode 100644 index 00000000..66587f77 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/AbstractTrivialMaskedMemory.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.memories; + +import java.util.Iterator; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.tuple.ITuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.matchers.util.IMemory; + +/** + * Common parts of nullary and identity specializations. + * + * @noextend This class is not intended to be subclassed by clients. + * @author Gabor Bergmann + * @since 2.0 + */ +abstract class AbstractTrivialMaskedMemory> extends MaskedTupleMemory { + + protected IMemory tuples; + + protected AbstractTrivialMaskedMemory(TupleMask mask, MemoryType bucketType, Object owner) { + super(mask, owner); + tuples = CollectionsFactory.createMemory(Object.class, bucketType); + } + + @Override + public Map> getWithTimeline(ITuple signature) { + throw new UnsupportedOperationException("Timeless memories do not support timestamp-based lookup!"); + } + + @Override + public void clear() { + tuples.clear(); + } + + @Override + public int getTotalSize() { + return tuples.size(); + } + + @Override + public Iterator iterator() { + return tuples.iterator(); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/DefaultMaskedTupleMemory.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/DefaultMaskedTupleMemory.java new file mode 100644 index 00000000..92081409 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/DefaultMaskedTupleMemory.java @@ -0,0 +1,127 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.memories; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.tuple.ITuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType; +import tools.refinery.viatra.runtime.matchers.util.IMemoryView; +import tools.refinery.viatra.runtime.matchers.util.IMultiLookup; +import tools.refinery.viatra.runtime.matchers.util.IMultiLookup.ChangeGranularity; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; + +/** + * @author Gabor Bergmann + * + * Default implementation that covers all cases. + * + * @since 2.0 + */ +public final class DefaultMaskedTupleMemory> + extends MaskedTupleMemory { + /** + * Maps a signature tuple to the bucket of tuples with the given signature. + * + * @since 2.0 + */ + protected IMultiLookup signatureToTuples; + + /** + * @param mask + * The mask used to index the matchings + * @param owner + * the object "owning" this memory + * @param bucketType + * the kind of tuple collection maintained for each indexer bucket + * @since 2.0 + */ + public DefaultMaskedTupleMemory(TupleMask mask, MemoryType bucketType, Object owner) { + super(mask, owner); + signatureToTuples = CollectionsFactory. createMultiLookup(Object.class, bucketType, Object.class); + } + + @Override + public boolean add(Tuple tuple) { + Tuple signature = mask.transform(tuple); + return add(tuple, signature); + } + + @Override + public boolean add(Tuple tuple, Tuple signature) { + try { + return signatureToTuples.addPair(signature, tuple) == ChangeGranularity.KEY; + } catch (IllegalStateException ex) { // ignore worthless internal exception details + throw raiseDuplicateInsertion(tuple); + } + + } + + @Override + public boolean remove(Tuple tuple) { + Tuple signature = mask.transform(tuple); + return remove(tuple, signature); + } + + @Override + public boolean remove(Tuple tuple, Tuple signature) { + try { + return signatureToTuples.removePair(signature, tuple) == ChangeGranularity.KEY; + } catch (IllegalStateException ex) { // ignore worthless internal exception details + throw raiseDuplicateDeletion(tuple); + } + } + + @Override + public Map> getWithTimeline(ITuple signature) { + throw new UnsupportedOperationException("Timeless memories do not support timestamp-based lookup!"); + } + + @Override + public Collection get(ITuple signature) { + IMemoryView bucket = signatureToTuples.lookupUnsafe(signature); + return bucket == null ? null : bucket.distinctValues(); + } + + @Override + public void clear() { + signatureToTuples.clear(); + } + + @Override + public Iterable getSignatures() { + return signatureToTuples.distinctKeys(); + } + + @Override + public Iterator iterator() { + return signatureToTuples.distinctValues().iterator(); + } + + @Override + public int getTotalSize() { + int i = 0; + for (Tuple key : signatureToTuples.distinctKeys()) { + i += signatureToTuples.lookup(key).size(); + } + return i; + } + + @Override + public int getKeysetSize() { + return signatureToTuples.countKeys(); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/IdentityMaskedTupleMemory.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/IdentityMaskedTupleMemory.java new file mode 100644 index 00000000..dc59daf5 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/IdentityMaskedTupleMemory.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.memories; + +import java.util.Collection; +import java.util.Collections; + +import tools.refinery.viatra.runtime.matchers.tuple.ITuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType; + +/** + * Specialized for identity mask; tuples are stored as a simple set/multiset memory. + * + * @author Gabor Bergmann + * @since 2.0 + */ +public final class IdentityMaskedTupleMemory> extends AbstractTrivialMaskedMemory { + + /** + * @param mask + * The mask used to index the matchings + * @param owner the object "owning" this memory + * @param bucketType the kind of tuple collection maintained for each indexer bucket + * @since 2.0 + */ + public IdentityMaskedTupleMemory(TupleMask mask, MemoryType bucketType, Object owner) { + super(mask, bucketType, owner); + if (!mask.isIdentity()) throw new IllegalArgumentException(mask.toString()); + } + + @Override + public int getKeysetSize() { + return tuples.size(); + } + + @Override + public Iterable getSignatures() { + return tuples; + } + + @Override + public Collection get(ITuple signature) { + Tuple contained = tuples.theContainedVersionOfUnsafe(signature); + return contained != null ? + Collections.singleton(contained) : + null; + } + + @Override + public boolean remove(Tuple tuple, Tuple signature) { + return tuples.removeOne(tuple); + } + + @Override + public boolean remove(Tuple tuple) { + return tuples.removeOne(tuple); + } + + @Override + public boolean add(Tuple tuple, Tuple signature) { + return tuples.addOne(tuple); + } + + @Override + public boolean add(Tuple tuple) { + return tuples.addOne(tuple); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/MaskedTupleMemory.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/MaskedTupleMemory.java new file mode 100644 index 00000000..62377624 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/MaskedTupleMemory.java @@ -0,0 +1,385 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.memories; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.memories.timely.TimelyDefaultMaskedTupleMemory; +import tools.refinery.viatra.runtime.matchers.memories.timely.TimelyIdentityMaskedTupleMemory; +import tools.refinery.viatra.runtime.matchers.memories.timely.TimelyNullaryMaskedTupleMemory; +import tools.refinery.viatra.runtime.matchers.memories.timely.TimelyUnaryMaskedTupleMemory; +import tools.refinery.viatra.runtime.matchers.tuple.ITuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.Clearable; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType; +import tools.refinery.viatra.runtime.matchers.util.resumable.MaskedResumable; +import tools.refinery.viatra.runtime.matchers.util.timeline.Diff; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; + +/** + * Indexes a collection of tuples by their signature (i.e. footprint, projection) obtained according to a mask. May + * belong to an "owner" (for documentation / traceability purposes). + *

    + * There are timeless and timely versions of the different memories. Timely versions associate {@link Timeline}s with + * the stored tuples. + * + * @noextend This class is not intended to be subclassed by clients. + * @author Gabor Bergmann + * @author Tamas Szabo + * @since 2.0 + */ +public abstract class MaskedTupleMemory> + implements Clearable, MaskedResumable { + + /** + * Creates a new memory for the given owner that indexes tuples according to the given mask. + */ + public static > MaskedTupleMemory create(final TupleMask mask, + final MemoryType bucketType, final Object owner) { + return create(mask, bucketType, owner, false); + } + + /** + * Creates a new memory for the given owner that indexes tuples according to the given mask. Clients can specify if + * the created memory should be timely or not.
    + *
    + * Timely means that tuples are associated with a timeline. + * + * @since 2.3 + */ + public static > MaskedTupleMemory create(final TupleMask mask, + final MemoryType bucketType, final Object owner, final boolean isTimely) { + return create(mask, bucketType, owner, isTimely, false); + } + + /** + * Creates a new memory for the given owner that indexes tuples according to the given mask. Clients can specify if + * the created memory should be timely or not. In case of timely memory, clients can also specify if the memory is + * lazy or not.
    + *
    + * Timely means that tuples are associated with a timeline.
    + *
    + * Lazyness can only be used together with timely memories. It means that the maintenance of the timelines is lazy, + * that is, the memory only updates its internal data structures at the timestamp affected by an update, and can be + * instructed later to resume the maintenance at higher timestamps, as well. + * + * @since 2.4 + */ + public static > MaskedTupleMemory create(final TupleMask mask, + final MemoryType bucketType, final Object owner, final boolean isTimely, final boolean isLazy) { + if (isTimely) { + if (bucketType != MemoryType.SETS) { + throw new IllegalArgumentException("Timely memories only support SETS as the bucket type!"); + } + if (mask.isIdentity()) { + return new TimelyIdentityMaskedTupleMemory(mask, owner, isLazy); + } else if (0 == mask.getSize()) { + return new TimelyNullaryMaskedTupleMemory(mask, owner, isLazy); + } else if (1 == mask.getSize()) { + return new TimelyUnaryMaskedTupleMemory(mask, owner, isLazy); + } else { + return new TimelyDefaultMaskedTupleMemory(mask, owner, isLazy); + } + } else { + if (isLazy) { + throw new IllegalArgumentException("Lazy maintenance is only supported by timely memories!"); + } + if (mask.isIdentity()) { + return new IdentityMaskedTupleMemory(mask, bucketType, owner); + } else if (0 == mask.getSize()) { + return new NullaryMaskedTupleMemory(mask, bucketType, owner); + } else if (1 == mask.getSize()) { + return new UnaryMaskedTupleMemory(mask, bucketType, owner); + } else { + return new DefaultMaskedTupleMemory(mask, bucketType, owner); + } + } + } + + @Override + public Map>> resumeAt(final Timestamp timestamp) { + throw new UnsupportedOperationException("This is only supported by lazy timely memory implementations!"); + } + + @Override + public Iterable getResumableSignatures() { + throw new UnsupportedOperationException("This is only supported by lazy timely memory implementations!"); + } + + @Override + public Timestamp getResumableTimestamp() { + return null; + } + + /** + * Initializes the contents of this memory based on the contents of another memory. The default value is associated + * with each tuple in the timely memories. + * + * @since 2.3 + */ + public void initializeWith(final MaskedTupleMemory other, final Timestamp defaultValue) { + throw new UnsupportedOperationException("This is only supported by timely memory implementations!"); + } + + /** + * Returns true if there is any tuple with the given signature that is present at the timestamp +inf, false + * otherwise. + * @since 2.4 + */ + public boolean isPresentAtInfinity(final ITuple signature) { + return get(signature) != null; + } + + /** + * Returns true of this memory is timely, false otherwise. + * + * @since 2.3 + */ + public boolean isTimely() { + return false; + } + + /** + * The mask by which the tuples are indexed. + */ + protected final TupleMask mask; + + /** + * The object "owning" this memory. May be null. + * + * @since 1.7 + */ + protected final Object owner; + + /** + * The node owning this memory. May be null. + * + * @since 2.0 + */ + public Object getOwner() { + return owner; + } + + /** + * The mask according to which tuples are projected and indexed. + * + * @since 2.0 + */ + public TupleMask getMask() { + return mask; + } + + /** + * @return the number of distinct signatures of all stored tuples. + */ + public abstract int getKeysetSize(); + + /** + * @return the total number of distinct tuples stored. Multiple copies of the same tuple, if allowed, are counted as + * one. + * + *

    + * This is currently not cached but computed on demand. It is therefore not efficient, and shall only be + * used for debug / profiling purposes. + */ + public abstract int getTotalSize(); + + /** + * Iterates over distinct tuples stored in the memory, regardless of their signatures. + */ + public abstract Iterator iterator(); + + /** + * Retrieves a read-only view of exactly those signatures for which at least one tuple is stored + * + * @since 2.0 + */ + public abstract Iterable getSignatures(); + + /** + * Retrieves tuples that have the specified signature + * + * @return collection of tuples found, null if none + */ + public abstract Collection get(final ITuple signature); + + /** + * Retrieves the tuples and their associated timelines that have the specified signature. + * + * @return the mappings from tuples to timelines, null if there is no mapping for the signature + * @since 2.4 + */ + public abstract Map> getWithTimeline(final ITuple signature); + + /** + * Retrieves tuples that have the specified signature. + * + * @return collection of tuples found, never null + * @since 2.1 + */ + public Collection getOrEmpty(final ITuple signature) { + final Collection result = get(signature); + return result == null ? Collections.emptySet() : result; + } + + /** + * Retrieves tuples with their associated timelines that have the specified signature. + * + * @return map of tuples and timelines found, never null + * @since 2.4 + */ + public Map> getOrEmptyWithTimeline(final ITuple signature) { + final Map> result = getWithTimeline(signature); + return result == null ? Collections.emptyMap() : result; + } + + /** + * Removes a tuple occurrence from the memory with the given signature. + * + * @param tuple + * the tuple to be removed from the memory + * @param signature + * precomputed footprint of the tuple according to the mask + * + * @return true if this was the the last occurrence of the signature (according to the mask) + */ + public boolean remove(final Tuple tuple, final Tuple signature) { + throw new UnsupportedOperationException("This is only supported by timeless memory implementations!"); + } + + /** + * Removes a tuple occurrence from the memory with the given signature and timestamp. + * + * @param tuple + * the tuple to be removed from the memory + * @param signature + * precomputed footprint of the tuple according to the mask + * @param timestamp + * the timestamp associated with the tuple + * + * @return A {@link Diff} describing how the timeline of the given tuple changed. + * + * @since 2.4 + */ + public Diff removeWithTimestamp(final Tuple tuple, final Tuple signature, final Timestamp timestamp) { + throw new UnsupportedOperationException("This is only supported by timely memory implementations!"); + } + + /** + * Removes a tuple occurrence from the memory. + * + * @param tuple + * the tuple to be removed from the memory + * + * @return true if this was the the last occurrence of the signature (according to the mask) + */ + public boolean remove(final Tuple tuple) { + throw new UnsupportedOperationException("This is only supported by timeless memory implementations!"); + } + + /** + * Removes a tuple occurrence from the memory with the given timestamp. + * + * @param tuple + * the tuple to be removed from the memory + * @param timestamp + * the timestamp associated with the tuple + * + * @return A {@link Diff} describing how the timeline of the given tuple changed. + * + * @since 2.4 + */ + public Diff removeWithTimestamp(final Tuple tuple, final Timestamp timestamp) { + throw new UnsupportedOperationException("This is only supported by timely memory implementations!"); + } + + /** + * Adds a tuple occurrence to the memory with the given signature. + * + * @param tuple + * the tuple to be added to the memory + * @param signature + * precomputed footprint of the tuple according to the mask + * + * @return true if new signature encountered (according to the mask) + */ + public boolean add(final Tuple tuple, final Tuple signature) { + throw new UnsupportedOperationException("This is only supported by timeless memory implementations!"); + } + + /** + * Adds a tuple occurrence to the memory with the given signature and timestamp. + * + * @param tuple + * the tuple to be added to the memory + * @param signature + * precomputed footprint of the tuple according to the mask + * @param timestamp + * the timestamp associated with the tuple + * + * @return A {@link Diff} describing how the timeline of the given tuple changed. + * + * @since 2.4 + */ + public Diff addWithTimestamp(final Tuple tuple, final Tuple signature, final Timestamp timestamp) { + throw new UnsupportedOperationException("This is only supported by timely memory implementations!"); + } + + /** + * Adds a tuple occurrence to the memory. + * + * @param tuple + * the tuple to be added to the memory + * + * @return true if new signature encountered (according to the mask) + */ + public boolean add(final Tuple tuple) { + throw new UnsupportedOperationException("This is only supported by timeless memory implementations!"); + } + + /** + * Adds a tuple occurrence to the memory with the given timestamp. + * + * @param tuple + * the tuple to be added to the memory + * @param timestamp + * the timestamp associated with the tuple + * + * @return A {@link Diff} describing how the timeline of the given tuple changed. + * + * @since 2.4 + */ + public Diff addWithTimestamp(final Tuple tuple, final Timestamp timestamp) { + throw new UnsupportedOperationException("This is only supported by timely memory implementations!"); + } + + protected MaskedTupleMemory(final TupleMask mask, final Object owner) { + super(); + this.mask = mask; + this.owner = owner; + } + + protected IllegalStateException raiseDuplicateInsertion(final Tuple tuple) { + return new IllegalStateException(String.format("Duplicate insertion of tuple %s into %s", tuple, owner)); + } + + protected IllegalStateException raiseDuplicateDeletion(final Tuple tuple) { + return new IllegalStateException(String.format("Duplicate deletion of tuple %s from %s", tuple, owner)); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "<" + mask + ">@" + owner; + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/NullaryMaskedTupleMemory.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/NullaryMaskedTupleMemory.java new file mode 100644 index 00000000..7fa9e053 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/NullaryMaskedTupleMemory.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.memories; + +import java.util.Collection; +import java.util.Collections; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.tuple.ITuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType; + +/** + * Specialized for nullary mask; tuples are stored as a simple set/multiset memory. + * + * @author Gabor Bergmann + * @since 2.0 + */ +public final class NullaryMaskedTupleMemory> extends AbstractTrivialMaskedMemory { + + protected static final Set UNIT_RELATION = + Collections.singleton(Tuples.staticArityFlatTupleOf()); + protected static final Set EMPTY_RELATION = + Collections.emptySet(); + /** + * @param mask + * The mask used to index the matchings + * @param owner the object "owning" this memory + * @param bucketType the kind of tuple collection maintained for each indexer bucket + * @since 2.0 + */ + public NullaryMaskedTupleMemory(TupleMask mask, MemoryType bucketType, Object owner) { + super(mask, bucketType, owner); + if (0 != mask.getSize()) throw new IllegalArgumentException(mask.toString()); + } + + @Override + public int getKeysetSize() { + return tuples.isEmpty() ? 0 : 1; + } + + @Override + public Iterable getSignatures() { + return tuples.isEmpty() ? EMPTY_RELATION : UNIT_RELATION; + } + + @Override + public Collection get(ITuple signature) { + if (0 == signature.getSize()) + return tuples.distinctValues(); + else return null; + } + + @Override + public boolean remove(Tuple tuple, Tuple signature) { + tuples.removeOne(tuple); + return tuples.isEmpty(); + } + + @Override + public boolean remove(Tuple tuple) { + return remove(tuple, null); + } + + @Override + public boolean add(Tuple tuple, Tuple signature) { + boolean wasEmpty = tuples.isEmpty(); + tuples.addOne(tuple); + return wasEmpty; + } + + @Override + public boolean add(Tuple tuple) { + return add(tuple, null); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/UnaryMaskedTupleMemory.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/UnaryMaskedTupleMemory.java new file mode 100644 index 00000000..f34cc9e3 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/UnaryMaskedTupleMemory.java @@ -0,0 +1,143 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.memories; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.tuple.ITuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType; +import tools.refinery.viatra.runtime.matchers.util.IMemoryView; +import tools.refinery.viatra.runtime.matchers.util.IMultiLookup; +import tools.refinery.viatra.runtime.matchers.util.IMultiLookup.ChangeGranularity; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; + +/** + * Specialized for unary mask; tuples are indexed by a single column as opposed to a projection (signature) tuple. + * + * @author Gabor Bergmann + * @since 2.0 + */ +public final class UnaryMaskedTupleMemory> extends MaskedTupleMemory { + + protected IMultiLookup columnToTuples; + protected final int keyPosition; + + /** + * @param mask + * The mask used to index the matchings + * @param owner the object "owning" this memory + * @param bucketType the kind of tuple collection maintained for each indexer bucket + * @since 2.0 + */ + public UnaryMaskedTupleMemory(TupleMask mask, MemoryType bucketType, Object owner) { + super(mask, owner); + if (1 != mask.getSize()) throw new IllegalArgumentException(mask.toString()); + + columnToTuples = CollectionsFactory.createMultiLookup( + Object.class, bucketType, Object.class); + keyPosition = mask.indices[0]; + } + + @Override + public void clear() { + columnToTuples.clear(); + } + + @Override + public int getKeysetSize() { + return columnToTuples.countKeys(); + } + + @Override + public int getTotalSize() { + int i = 0; + for (Object key : columnToTuples.distinctKeys()) { + i += columnToTuples.lookup(key).size(); + } + return i; + } + + @Override + public Iterator iterator() { + return columnToTuples.distinctValues().iterator(); + } + + @Override + public Iterable getSignatures() { + return () -> { + Iterator wrapped = columnToTuples.distinctKeys().iterator(); + return new Iterator() { + @Override + public boolean hasNext() { + return wrapped.hasNext(); + } + @Override + public Tuple next() { + Object key = wrapped.next(); + return Tuples.staticArityFlatTupleOf(key); + } + }; + }; + } + + @Override + public Collection get(ITuple signature) { + Object key = signature.get(0); + IMemoryView bucket = columnToTuples.lookup(key); + return bucket == null ? null : bucket.distinctValues(); + } + + @Override + public Map> getWithTimeline(ITuple signature) { + throw new UnsupportedOperationException("Timeless memories do not support timestamp-based lookup!"); + } + + @Override + public boolean remove(Tuple tuple, Tuple signature) { + return removeInternal(tuple, tuple.get(keyPosition)); + } + + @Override + public boolean remove(Tuple tuple) { + return removeInternal(tuple, tuple.get(keyPosition)); + } + + @Override + public boolean add(Tuple tuple, Tuple signature) { + return addInternal(tuple, tuple.get(keyPosition)); + } + + @Override + public boolean add(Tuple tuple) { + return addInternal(tuple, tuple.get(keyPosition)); + } + + protected boolean addInternal(Tuple tuple, Object key) { + try { + return columnToTuples.addPair(key, tuple) == ChangeGranularity.KEY; + } catch (IllegalStateException ex) { // ignore worthless internal exception details + throw raiseDuplicateInsertion(tuple); + } + } + + protected boolean removeInternal(Tuple tuple, Object key) { + try { + return columnToTuples.removePair(key, tuple) == ChangeGranularity.KEY; + } catch (IllegalStateException ex) { // ignore worthless internal exception details + throw raiseDuplicateDeletion(tuple); + } + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/AbstractTimelyMaskedMemory.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/AbstractTimelyMaskedMemory.java new file mode 100644 index 00000000..45ce3a4e --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/AbstractTimelyMaskedMemory.java @@ -0,0 +1,228 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.memories.timely; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.TreeMap; + +import tools.refinery.viatra.runtime.matchers.memories.MaskedTupleMemory; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.Signed; +import tools.refinery.viatra.runtime.matchers.util.TimelyMemory; +import tools.refinery.viatra.runtime.matchers.util.timeline.Diff; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; + +/** + * Common parts of timely default and timely unary implementations. + * + * @noextend This class is not intended to be subclassed by clients. + * @author Tamas Szabo + * @since 2.3 + */ +abstract class AbstractTimelyMaskedMemory, KeyType> + extends MaskedTupleMemory { + + protected final TreeMap> foldingStates; + protected final Map> memoryMap; + protected final boolean isLazy; + + public AbstractTimelyMaskedMemory(final TupleMask mask, final Object owner, final boolean isLazy) { + super(mask, owner); + this.isLazy = isLazy; + this.memoryMap = CollectionsFactory.createMap(); + this.foldingStates = this.isLazy ? CollectionsFactory.createTreeMap() : null; + } + + @Override + public void initializeWith(final MaskedTupleMemory other, final Timestamp defaultValue) { + final Iterable signatures = other.getSignatures(); + for (final Tuple signature : signatures) { + if (other.isTimely()) { + final Map> tupleMap = other.getWithTimeline(signature); + for (final Entry> entry : tupleMap.entrySet()) { + for (final Signed signed : entry.getValue().asChangeSequence()) { + if (signed.getDirection() == Direction.DELETE) { + this.removeWithTimestamp(entry.getKey(), signed.getPayload()); + } else { + this.addWithTimestamp(entry.getKey(), signed.getPayload()); + } + } + } + } else { + final Collection tuples = other.get(signature); + for (final Tuple tuple : tuples) { + this.addWithTimestamp(tuple, defaultValue); + } + } + } + } + + public boolean isPresentAtInfinityInteral(KeyType key) { + final TimelyMemory values = this.memoryMap.get(key); + if (values == null) { + return false; + } else { + return values.getCountAtInfinity() != 0; + } + } + + @Override + public void clear() { + this.memoryMap.clear(); + } + + @Override + public int getKeysetSize() { + return this.memoryMap.keySet().size(); + } + + @Override + public int getTotalSize() { + int i = 0; + for (final Entry> entry : this.memoryMap.entrySet()) { + i += entry.getValue().size(); + } + return i; + } + + @Override + public Iterator iterator() { + return this.memoryMap.values().stream().flatMap(e -> e.keySet().stream()).iterator(); + } + + protected Collection getInternal(final KeyType key) { + final TimelyMemory memory = this.memoryMap.get(key); + if (memory == null) { + return null; + } else { + return memory.getTuplesAtInfinity(); + } + } + + public Map> getWithTimestampInternal(final KeyType key) { + final TimelyMemory memory = this.memoryMap.get(key); + if (memory == null) { + return null; + } else { + return memory.asMap(); + } + } + + protected Diff removeInternal(final KeyType key, final Tuple tuple, final Timestamp timestamp) { + Timestamp oldResumableTimestamp = null; + Timestamp newResumableTimestamp = null; + + final TimelyMemory keyMemory = this.memoryMap.get(key); + if (keyMemory == null) { + throw raiseDuplicateDeletion(tuple); + } + + if (this.isLazy) { + oldResumableTimestamp = keyMemory.getResumableTimestamp(); + } + + Diff diff = null; + try { + diff = keyMemory.remove(tuple, timestamp); + } catch (final IllegalStateException e) { + throw raiseDuplicateDeletion(tuple); + } + if (keyMemory.isEmpty()) { + this.memoryMap.remove(key); + } + + if (this.isLazy) { + newResumableTimestamp = keyMemory.getResumableTimestamp(); + if (!Objects.equals(oldResumableTimestamp, newResumableTimestamp)) { + unregisterFoldingState(oldResumableTimestamp, key); + registerFoldingState(newResumableTimestamp, key); + } + } + + return diff; + } + + protected Diff addInternal(final KeyType key, final Tuple tuple, final Timestamp timestamp) { + Timestamp oldResumableTimestamp = null; + Timestamp newResumableTimestamp = null; + + final TimelyMemory keyMemory = this.memoryMap.computeIfAbsent(key, + k -> new TimelyMemory(this.isLazy)); + + if (this.isLazy) { + oldResumableTimestamp = keyMemory.getResumableTimestamp(); + } + + final Diff diff = keyMemory.put(tuple, timestamp); + + if (this.isLazy) { + newResumableTimestamp = keyMemory.getResumableTimestamp(); + if (!Objects.equals(oldResumableTimestamp, newResumableTimestamp)) { + unregisterFoldingState(oldResumableTimestamp, key); + registerFoldingState(newResumableTimestamp, key); + } + } + + return diff; + } + + @Override + public Diff removeWithTimestamp(final Tuple tuple, final Timestamp timestamp) { + return removeWithTimestamp(tuple, null, timestamp); + } + + @Override + public Diff addWithTimestamp(final Tuple tuple, final Timestamp timestamp) { + return addWithTimestamp(tuple, null, timestamp); + } + + @Override + public boolean isTimely() { + return true; + } + + protected void registerFoldingState(final Timestamp timestamp, final KeyType key) { + if (timestamp != null) { + this.foldingStates.compute(timestamp, (k, v) -> { + if (v == null) { + v = CollectionsFactory.createSet(); + } + v.add(key); + return v; + }); + } + } + + protected void unregisterFoldingState(final Timestamp timestamp, final KeyType key) { + if (timestamp != null) { + this.foldingStates.compute(timestamp, (k, v) -> { + v.remove(key); + return v.isEmpty() ? null : v; + }); + } + } + + @Override + public Timestamp getResumableTimestamp() { + if (this.foldingStates == null || this.foldingStates.isEmpty()) { + return null; + } else { + return this.foldingStates.firstKey(); + } + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/AbstractTimelyTrivialMaskedMemory.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/AbstractTimelyTrivialMaskedMemory.java new file mode 100644 index 00000000..ca06685a --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/AbstractTimelyTrivialMaskedMemory.java @@ -0,0 +1,100 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.memories.timely; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + +import tools.refinery.viatra.runtime.matchers.memories.MaskedTupleMemory; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.Signed; +import tools.refinery.viatra.runtime.matchers.util.TimelyMemory; +import tools.refinery.viatra.runtime.matchers.util.timeline.Diff; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; + +/** + * Common parts of timely nullary and timely identity implementations. + * + * @noextend This class is not intended to be subclassed by clients. + * @author Tamas Szabo + * @since 2.3 + */ +abstract class AbstractTimelyTrivialMaskedMemory> extends MaskedTupleMemory { + + protected final TimelyMemory memory; + + protected AbstractTimelyTrivialMaskedMemory(final TupleMask mask, final Object owner, final boolean isLazy) { + super(mask, owner); + this.memory = new TimelyMemory(isLazy); + } + + @Override + public void initializeWith(final MaskedTupleMemory other, final Timestamp defaultValue) { + final Iterable signatures = other.getSignatures(); + for (final Tuple signature : signatures) { + if (other.isTimely()) { + final Map> tupleMap = other.getWithTimeline(signature); + for (final Entry> entry : tupleMap.entrySet()) { + for (final Signed signed : entry.getValue().asChangeSequence()) { + if (signed.getDirection() == Direction.DELETE) { + this.removeWithTimestamp(entry.getKey(), signed.getPayload()); + } else { + this.addWithTimestamp(entry.getKey(), signed.getPayload()); + } + } + } + } else { + final Collection tuples = other.get(signature); + for (final Tuple tuple : tuples) { + this.removeWithTimestamp(tuple, defaultValue); + } + } + } + } + + @Override + public void clear() { + this.memory.clear(); + } + + @Override + public int getTotalSize() { + return this.memory.size(); + } + + @Override + public Iterator iterator() { + return this.memory.keySet().iterator(); + } + + @Override + public Diff removeWithTimestamp(final Tuple tuple, final Timestamp timestamp) { + return removeWithTimestamp(tuple, null, timestamp); + } + + @Override + public Diff addWithTimestamp(final Tuple tuple, final Timestamp timestamp) { + return addWithTimestamp(tuple, null, timestamp); + } + + @Override + public boolean isTimely() { + return true; + } + + @Override + public Timestamp getResumableTimestamp() { + return this.memory.getResumableTimestamp(); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/TimelyDefaultMaskedTupleMemory.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/TimelyDefaultMaskedTupleMemory.java new file mode 100644 index 00000000..623d7399 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/TimelyDefaultMaskedTupleMemory.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.memories.timely; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.tuple.ITuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.TimelyMemory; +import tools.refinery.viatra.runtime.matchers.util.timeline.Diff; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; + +/** + * Default timely implementation that covers all cases. + * + * @author Tamas Szabo + * @since 2.3 + */ +public final class TimelyDefaultMaskedTupleMemory> + extends AbstractTimelyMaskedMemory { + + public TimelyDefaultMaskedTupleMemory(final TupleMask mask, final Object owner, final boolean isLazy) { + super(mask, owner, isLazy); + } + + @Override + public Iterable getSignatures() { + return this.memoryMap.keySet(); + } + + @Override + public Diff removeWithTimestamp(final Tuple tuple, final Tuple signature, + final Timestamp timestamp) { + final Tuple key = mask.transform(tuple); + return removeInternal(key, tuple, timestamp); + } + + @Override + public Diff addWithTimestamp(final Tuple tuple, final Tuple signature, + final Timestamp timestamp) { + final Tuple key = this.mask.transform(tuple); + return addInternal(key, tuple, timestamp); + } + + @Override + public Collection get(final ITuple signature) { + return getInternal(signature.toImmutable()); + } + + @Override + public Map> getWithTimeline(final ITuple signature) { + return getWithTimestampInternal(signature.toImmutable()); + } + + @Override + public boolean isPresentAtInfinity(final ITuple signature) { + return isPresentAtInfinityInteral(signature.toImmutable()); + } + + @Override + public Set getResumableSignatures() { + if (this.foldingStates == null || this.foldingStates.isEmpty()) { + return Collections.emptySet(); + } else { + return this.foldingStates.firstEntry().getValue(); + } + } + + @Override + public Map>> resumeAt(final Timestamp timestamp) { + final Map>> result = CollectionsFactory.createMap(); + final Timestamp resumableTimestamp = this.getResumableTimestamp(); + if (resumableTimestamp == null || resumableTimestamp.compareTo(timestamp) != 0) { + throw new IllegalStateException("Expected to continue folding at " + resumableTimestamp + "!"); + } + final Set signatures = this.foldingStates.remove(timestamp); + for (final Tuple signature : signatures) { + final TimelyMemory memory = this.memoryMap.get(signature); + final Map> diffMap = memory.resumeAt(resumableTimestamp); + result.put(signature, diffMap); + registerFoldingState(memory.getResumableTimestamp(), signature); + } + return result; + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/TimelyIdentityMaskedTupleMemory.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/TimelyIdentityMaskedTupleMemory.java new file mode 100644 index 00000000..568f274d --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/TimelyIdentityMaskedTupleMemory.java @@ -0,0 +1,106 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.memories.timely; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.tuple.ITuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.timeline.Diff; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; + +/** + * Timely specialization for identity mask. + * + * @author Tamas Szabo + * @since 2.3 + */ +public final class TimelyIdentityMaskedTupleMemory> + extends AbstractTimelyTrivialMaskedMemory { + + public TimelyIdentityMaskedTupleMemory(final TupleMask mask, final Object owner, final boolean isLazy) { + super(mask, owner, isLazy); + if (!mask.isIdentity()) + throw new IllegalArgumentException(mask.toString()); + } + + @Override + public int getKeysetSize() { + return this.memory.size(); + } + + @Override + public Iterable getSignatures() { + return this.memory.keySet(); + } + + @Override + public Collection get(final ITuple signature) { + if (this.memory.getTuplesAtInfinity().contains(signature)) { + return Collections.singleton(signature.toImmutable()); + } else { + return null; + } + } + + @Override + public Map> getWithTimeline(final ITuple signature) { + final Timeline value = this.memory.get(signature); + if (value != null) { + return Collections.singletonMap(signature.toImmutable(), value); + } else { + return null; + } + } + + @Override + public Diff removeWithTimestamp(final Tuple tuple, final Tuple signature, final Timestamp timestamp) { + try { + return this.memory.remove(tuple, timestamp); + } catch (final IllegalStateException e) { + throw raiseDuplicateDeletion(tuple); + } + } + + @Override + public Diff addWithTimestamp(final Tuple tuple, final Tuple signature, final Timestamp timestamp) { + return this.memory.put(tuple, timestamp); + } + + @Override + public boolean isPresentAtInfinity(final ITuple signature) { + return this.memory.isPresentAtInfinity(signature.toImmutable()); + } + + @Override + public Set getResumableSignatures() { + if (this.memory.getResumableTimestamp() != null) { + return this.memory.getResumableTuples(); + } else { + return Collections.emptySet(); + } + } + + @Override + public Map>> resumeAt(final Timestamp timestamp) { + final Map> diffMap = this.memory.resumeAt(timestamp); + final Map>> result = CollectionsFactory.createMap(); + for (final Entry> entry : diffMap.entrySet()) { + result.put(entry.getKey(), Collections.singletonMap(entry.getKey(), entry.getValue())); + } + return result; + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/TimelyNullaryMaskedTupleMemory.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/TimelyNullaryMaskedTupleMemory.java new file mode 100644 index 00000000..75987a89 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/TimelyNullaryMaskedTupleMemory.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.memories.timely; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.tuple.ITuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.timeline.Diff; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; + +/** + * Timely specialization for nullary mask. + * + * @author Tamas Szabo + * @since 2.3 + */ +public final class TimelyNullaryMaskedTupleMemory> + extends AbstractTimelyTrivialMaskedMemory { + + protected static final Tuple EMPTY_TUPLE = Tuples.staticArityFlatTupleOf(); + protected static final Set UNIT_RELATION = Collections.singleton(EMPTY_TUPLE); + protected static final Set EMPTY_RELATION = Collections.emptySet(); + + public TimelyNullaryMaskedTupleMemory(final TupleMask mask, final Object owner, final boolean isLazy) { + super(mask, owner, isLazy); + if (0 != mask.getSize()) { + throw new IllegalArgumentException(mask.toString()); + } + } + + @Override + public int getKeysetSize() { + return this.memory.isEmpty() ? 0 : 1; + } + + @Override + public Iterable getSignatures() { + return this.memory.isEmpty() ? EMPTY_RELATION : UNIT_RELATION; + } + + @Override + public Collection get(final ITuple signature) { + if (0 == signature.getSize()) { + return this.memory.getTuplesAtInfinity(); + } else { + return null; + } + } + + @Override + public Map> getWithTimeline(final ITuple signature) { + if (0 == signature.getSize()) { + return this.memory.asMap(); + } else { + return null; + } + } + + @Override + public Diff removeWithTimestamp(final Tuple tuple, final Tuple signature, final Timestamp timestamp) { + try { + return this.memory.remove(tuple, timestamp); + } catch (final IllegalStateException e) { + throw raiseDuplicateDeletion(tuple); + } + } + + @Override + public Diff addWithTimestamp(final Tuple tuple, final Tuple signature, final Timestamp timestamp) { + return this.memory.put(tuple, timestamp); + } + + @Override + public boolean isPresentAtInfinity(final ITuple signature) { + if (0 == signature.getSize()) { + return this.memory.getCountAtInfinity() > 0; + } else { + return false; + } + } + + @Override + public Set getResumableSignatures() { + if (this.memory.getResumableTimestamp() != null) { + return UNIT_RELATION; + } else { + return EMPTY_RELATION; + } + } + + @Override + public Map>> resumeAt(final Timestamp timestamp) { + return Collections.singletonMap(EMPTY_TUPLE, this.memory.resumeAt(timestamp)); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/TimelyUnaryMaskedTupleMemory.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/TimelyUnaryMaskedTupleMemory.java new file mode 100644 index 00000000..178193af --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/memories/timely/TimelyUnaryMaskedTupleMemory.java @@ -0,0 +1,133 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.memories.timely; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.tuple.ITuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.TimelyMemory; +import tools.refinery.viatra.runtime.matchers.util.timeline.Diff; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; + +/** + * Timely specialization for unary mask. + * + * @author Tamas Szabo + * @since 2.3 + */ +public final class TimelyUnaryMaskedTupleMemory> + extends AbstractTimelyMaskedMemory { + + protected final int keyPosition; + + public TimelyUnaryMaskedTupleMemory(final TupleMask mask, final Object owner, final boolean isLazy) { + super(mask, owner, isLazy); + if (1 != mask.getSize()) + throw new IllegalArgumentException(mask.toString()); + this.keyPosition = mask.indices[0]; + } + + @Override + public Iterable getSignatures() { + return () -> { + final Iterator wrapped = this.memoryMap.keySet().iterator(); + return new Iterator() { + @Override + public boolean hasNext() { + return wrapped.hasNext(); + } + + @Override + public Tuple next() { + final Object key = wrapped.next(); + return Tuples.staticArityFlatTupleOf(key); + } + }; + }; + } + + @Override + public Diff removeWithTimestamp(final Tuple tuple, final Tuple signature, final Timestamp timestamp) { + final Object key = tuple.get(keyPosition); + return removeInternal(key, tuple, timestamp); + } + + @Override + public Diff addWithTimestamp(final Tuple tuple, final Tuple signature, final Timestamp timestamp) { + final Object key = tuple.get(keyPosition); + return addInternal(key, tuple, timestamp); + } + + @Override + public Collection get(final ITuple signature) { + return getInternal(signature.get(0)); + } + + @Override + public Map> getWithTimeline(final ITuple signature) { + return getWithTimestampInternal(signature.get(0)); + } + + @Override + public boolean isPresentAtInfinity(ITuple signature) { + return isPresentAtInfinityInteral(signature.get(0)); + } + + @Override + public Iterable getResumableSignatures() { + if (this.foldingStates == null || this.foldingStates.isEmpty()) { + return Collections.emptySet(); + } else { + return () -> { + final Iterator wrapped = this.foldingStates.firstEntry().getValue().iterator(); + return new Iterator() { + @Override + public boolean hasNext() { + return wrapped.hasNext(); + } + + @Override + public Tuple next() { + final Object key = wrapped.next(); + return Tuples.staticArityFlatTupleOf(key); + } + }; + }; + } + } + + @Override + public Map>> resumeAt(final Timestamp timestamp) { + final Map>> result = CollectionsFactory.createMap(); + final Timestamp resumableTimestamp = this.getResumableTimestamp(); + if (resumableTimestamp == null) { + throw new IllegalStateException("There is nothing to fold!"); + } else if (resumableTimestamp.compareTo(timestamp) != 0) { + throw new IllegalStateException("Expected to continue folding at " + resumableTimestamp + "!"); + } + + final Set signatures = this.foldingStates.remove(timestamp); + for (final Object signature : signatures) { + final TimelyMemory memory = this.memoryMap.get(signature); + final Map> diffMap = memory.resumeAt(resumableTimestamp); + result.put(Tuples.staticArityFlatTupleOf(signature), diffMap); + registerFoldingState(memory.getResumableTimestamp(), signature); + } + return result; + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/IOperationCompiler.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/IOperationCompiler.java new file mode 100644 index 00000000..c9f4b305 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/IOperationCompiler.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.planning; + +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; +import tools.refinery.viatra.runtime.matchers.psystem.IExpressionEvaluator; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; + +/** + * + * An implicit common parameter is the "effort" PatternDescription. This + * indicates that the build request is part of an effort to build the matcher of + * the given pattern; it it important to record this during code generation so + * that the generated code can be separated according to patterns. + * + * @param + * the handle of a receiver-like RETE ending to which plans can be + * connected + * @author Gabor Bergmann + * @noimplement This interface is not intended to be implemented by clients. + */ +public interface IOperationCompiler { + + /** + * @throws ViatraQueryRuntimeException + */ + public Collector patternCollector(PQuery pattern); + + public void buildConnection(SubPlan parentPlan, Collector collector); + + /** + * @since 0.9 + */ + public void patternFinished(PQuery pattern, Collector collector); + + /** + * @throws ViatraQueryRuntimeException + */ + public SubPlan patternCallPlan(Tuple nodes, PQuery supplierKey); + + public SubPlan transitiveInstantiationPlan(Tuple nodes); + + public SubPlan directInstantiationPlan(Tuple nodes); + + public SubPlan transitiveGeneralizationPlan(Tuple nodes); + + public SubPlan directGeneralizationPlan(Tuple nodes); + + public SubPlan transitiveContainmentPlan(Tuple nodes); + + public SubPlan directContainmentPlan(Tuple nodes); + + public SubPlan binaryEdgeTypePlan(Tuple nodes, Object supplierKey); + + public SubPlan ternaryEdgeTypePlan(Tuple nodes, Object supplierKey); + + public SubPlan unaryTypePlan(Tuple nodes, Object supplierKey); + + public SubPlan buildStartingPlan(Object[] constantValues, Object[] constantNames); + + public SubPlan buildEqualityChecker(SubPlan parentPlan, int[] indices); + + public SubPlan buildInjectivityChecker(SubPlan parentPlan, int subject, int[] inequalIndices); + + public SubPlan buildTransitiveClosure(SubPlan parentPlan); + + public SubPlan buildTrimmer(SubPlan parentPlan, TupleMask trimMask, boolean enforceUniqueness); + + public SubPlan buildBetaNode(SubPlan primaryPlan, SubPlan sidePlan, + TupleMask primaryMask, TupleMask sideMask, TupleMask complementer, boolean negative); + + public SubPlan buildCounterBetaNode(SubPlan primaryPlan, SubPlan sidePlan, + TupleMask primaryMask, TupleMask originalSideMask, TupleMask complementer, + Object aggregateResultCalibrationElement); + + public SubPlan buildCountCheckBetaNode(SubPlan primaryPlan, SubPlan sidePlan, + TupleMask primaryMask, TupleMask originalSideMask, int resultPositionInSignature); + + public SubPlan buildPredicateChecker(IExpressionEvaluator evaluator, Map tupleNameMap, + SubPlan parentPlan); + public SubPlan buildFunctionEvaluator(IExpressionEvaluator evaluator, Map tupleNameMap, + SubPlan parentPlan, Object computedResultCalibrationElement); + + /** + * @return an operation compiler that potentially acts on a separate container + */ + public IOperationCompiler getNextContainer(); + + /** + * @return an operation compiler that puts build actions on the tab of the given pattern + * @since 0.9 + */ + public IOperationCompiler putOnTab(PQuery effort /*, IPatternMatcherContext context*/); + + public void reinitialize(); + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/IQueryPlannerStrategy.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/IQueryPlannerStrategy.java new file mode 100644 index 00000000..6ce9d91b --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/IQueryPlannerStrategy.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.planning; + +import org.apache.log4j.Logger; +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; + +/** + * An algorithm that builds a query plan based on a PSystem representation of a body of constraints. This interface is + * for internal use of the various query backends. + * + * @author Gabor Bergmann + */ +public interface IQueryPlannerStrategy { + + /** + * @throws ViatraQueryRuntimeException + */ + public SubPlan plan(PBody pSystem, Logger logger, IQueryMetaContext context); +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/QueryProcessingException.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/QueryProcessingException.java new file mode 100644 index 00000000..501ddf73 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/QueryProcessingException.java @@ -0,0 +1,102 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.planning; + +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; + +/** + * @author Zoltan Ujhelyi + * @since 0.9 + */ +public class QueryProcessingException extends ViatraQueryRuntimeException { + + private static final long serialVersionUID = -8272290113656867086L; + /** + * Binding the '{n}' (n = 1..N) strings to contextual conditions in 'context' + * + * @param context + * : array of context-sensitive Strings + */ + protected static String bind(String message, String[] context) { + if (context == null) + return message; + + String internal = message; + for (int i = 0; i < context.length; i++) { + internal = internal.replace("{" + (i + 1) + "}", context[i] != null ? context[i] : "<>"); + } + return internal; + } + + private Object patternDescription; + private String shortMessage; + + /** + * @param message + * The template of the exception message + * @param context + * The data elements to be used to instantiate the template. Can be null if no context parameter is + * defined + * @param patternDescription + * the PatternDescription where the exception occurred + * @since 2.0 + */ + public QueryProcessingException(String message, Object patternDescription) { + super(message); + initializeFields(message, patternDescription); + } + + /** + * @param message + * The template of the exception message + * @param context + * The data elements to be used to instantiate the template. Can be null if no context parameter is + * defined + * @param patternDescription + * the PatternDescription where the exception occurred + */ + public QueryProcessingException(String message, String[] context, String shortMessage, Object patternDescription) { + super(bind(message, context)); + initializeFields(shortMessage, patternDescription); + } + + /** + * @param message + * The template of the exception message + * @param context + * The data elements to be used to instantiate the template. Can be null if no context parameter is + * defined + * @param patternDescription + * the PatternDescription where the exception occurred + */ + public QueryProcessingException(String message, String[] context, String shortMessage, Object patternDescription, + Throwable cause) { + super(bind(message, context), cause); + initializeFields(shortMessage, patternDescription); + } + + public Object getPatternDescription() { + return patternDescription; + } + + public String getShortMessage() { + return shortMessage; + } + + private void initializeFields(String shortMessage, Object patternDescription) { + this.patternDescription = patternDescription; + this.shortMessage = shortMessage; + } + + + public void setPatternDescription(Object patternDescription) { + this.patternDescription = patternDescription; + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/SubPlan.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/SubPlan.java new file mode 100644 index 00000000..1998df9d --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/SubPlan.java @@ -0,0 +1,240 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.planning; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.stream.Collectors; + +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.planning.helpers.TypeHelper; +import tools.refinery.viatra.runtime.matchers.planning.operations.POperation; +import tools.refinery.viatra.runtime.matchers.planning.operations.PProject; +import tools.refinery.viatra.runtime.matchers.planning.operations.PStart; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.TypeJudgement; + +/** + * A plan representing a subset of (or possibly all the) constraints evaluated. A SubPlan instance is responsible for + * representing a state of the plan; but after it is initialized it is expected be immutable + * (exception: inferred constraints, see {@link #inferConstraint(PConstraint)}). + * + *

    A SubPlan is constructed by applying a {@link POperation} on zero or more parent SubPlans. + * Important maintained information:

      + *
    • set of variables whose values are known when the runtime evaluation is at this stage, + *
    • set of constraints that are known to hold true at this point. + *
    + * + *

    Recommended to instantiate via a {@link SubPlanFactory} or subclasses, + * so that query planners can subclass SubPlan if needed. + * + * @author Gabor Bergmann + * + */ +public class SubPlan { + private PBody body; + private List parentPlans; + private POperation operation; + + private final Set visibleVariables; + private final Set allVariables; + private final Set introducedVariables; // delta compared to first parent + private Set allConstraints; + private Set deltaConstraints; // delta compared to all parents + private Set externallyInferredConstraints; // inferred in addition to direct consequences of the operation and parents + + + + + + /** + * A SubPlan is constructed by applying a {@link POperation} on zero or more parent SubPlans. + */ + public SubPlan(PBody body, POperation operation, SubPlan... parentPlans) { + this(body, operation, Arrays.asList(parentPlans)); + } + /** + * A SubPlan is constructed by applying a {@link POperation} on zero or more parent SubPlans. + */ + public SubPlan(PBody body, POperation operation, List parentPlans) { + super(); + this.body = body; + this.parentPlans = parentPlans; + this.operation = operation; + + this.externallyInferredConstraints = new HashSet(); + this.deltaConstraints = new HashSet(operation.getDeltaConstraints()); + + this.allVariables = new HashSet(); + for (PConstraint constraint: deltaConstraints) { + this.allVariables.addAll(constraint.getDeducedVariables()); + } + this.allConstraints = new HashSet(deltaConstraints); + for (SubPlan parentPlan: parentPlans) { + this.allConstraints.addAll(parentPlan.getAllEnforcedConstraints()); + this.allVariables.addAll(parentPlan.getAllDeducedVariables()); + } + + // TODO this is ugly a bit + if (operation instanceof PStart) { + this.visibleVariables = new HashSet(((PStart) operation).getAPrioriVariables()); + this.allVariables.addAll(visibleVariables); + } else if (operation instanceof PProject) { + this.visibleVariables = new HashSet(((PProject) operation).getToVariables()); + } else { + this.visibleVariables = new HashSet(); + for (SubPlan parentPlan: parentPlans) + this.visibleVariables.addAll(parentPlan.getVisibleVariables()); + for (PConstraint constraint: deltaConstraints) + this.visibleVariables.addAll(constraint.getDeducedVariables()); + } + + this.introducedVariables = new HashSet(this.visibleVariables); + if (!parentPlans.isEmpty()) + introducedVariables.removeAll(parentPlans.get(0).getVisibleVariables()); + + operation.checkConsistency(this); + } + + + @Override + public String toString() { + return toLongString(); + } + public String toShortString() { + return String.format("Plan{%s}:%s", + visibleVariables.stream().map(PVariable::getName).collect(Collectors.joining(",")), + operation.getShortName()); + } + public String toLongString() { + return String.format("%s<%s>", + toShortString(), + parentPlans.stream().map(Object::toString).collect(Collectors.joining("; "))); + } + + + /** + * All constraints that are known to hold at this point + */ + public Set getAllEnforcedConstraints() { + return allConstraints; + } + + /** + * The new constraints enforced at this stage of plan, that aren't yet enforced at parents + * (results are also included in {@link SubPlan#getAllEnforcedConstraints()}) + */ + public Set getDeltaEnforcedConstraints() { + return deltaConstraints; + } + + /** + * Indicate that a given constraint was found to be automatically satisfied at this point + * without additional operations. + * (results are also included in {@link SubPlan#getDeltaEnforcedConstraints()}) + * + *

    Warning: not propagated automatically to child plans, + * so best to invoke before constructing further SubPlans.

    + */ + public void inferConstraint(PConstraint constraint) { + externallyInferredConstraints.add(constraint); + deltaConstraints.add(constraint); + allConstraints.add(constraint); + } + + public PBody getBody() { + return body; + } + + /** + * Variables which are assigned a value at this point + * (results are also included in {@link SubPlan#getAllDeducedVariables()}) + */ + public Set getVisibleVariables() { + return visibleVariables; + } + /** + * Variables which have been assigned a value; + * includes visible variables (see {@link #getVisibleVariables()}) + * and additionally any variables hidden by a projection (see {@link PProject}). + */ + public Set getAllDeducedVariables() { + return allVariables; + } + /** + * Delta compared to first parent: variables that are visible here but were not visible at the first parent. + */ + public Set getIntroducedVariables() { + return introducedVariables; + } + public List getParentPlans() { + return parentPlans; + } + public POperation getOperation() { + return operation; + } + + + /** + * The closure of all type judgments of enforced constraints at this point. + *

    No subsumption applied. + */ + public Set getAllImpliedTypeJudgements(IQueryMetaContext context) { + Set impliedJudgements = allImpliedTypeJudgements.get(context); + if (impliedJudgements == null) { + Set equivalentJudgements = TypeHelper.getDirectJudgements(getAllEnforcedConstraints(), context); + impliedJudgements = TypeHelper.typeClosure(equivalentJudgements, context); + + allImpliedTypeJudgements.put(context, impliedJudgements); + } + return impliedJudgements; + } + private WeakHashMap> allImpliedTypeJudgements = new WeakHashMap>(); + + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + + ((operation == null) ? 0 : operation.hashCode()); + result = prime * result + + ((parentPlans == null) ? 0 : parentPlans.hashCode()); + return result; + } + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof SubPlan)) + return false; + SubPlan other = (SubPlan) obj; + if (operation == null) { + if (other.operation != null) + return false; + } else if (!operation.equals(other.operation)) + return false; + if (parentPlans == null) { + if (other.parentPlans != null) + return false; + } else if (!parentPlans.equals(other.parentPlans)) + return false; + return true; + } + + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/SubPlanFactory.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/SubPlanFactory.java new file mode 100644 index 00000000..d0df5fac --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/SubPlanFactory.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.planning; + +import tools.refinery.viatra.runtime.matchers.planning.operations.POperation; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; + +/** + * Single entry point for creating subplans. + * Can be subclassed by query planner to provide specialized SubPlans. + * @author Bergmann Gabor + * + */ +public class SubPlanFactory { + + protected PBody body; + + public SubPlanFactory(PBody body) { + super(); + this.body = body; + } + + public SubPlan createSubPlan(POperation operation, SubPlan... parentPlans) { + return new SubPlan(body, operation, parentPlans); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/helpers/BuildHelper.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/helpers/BuildHelper.java new file mode 100644 index 00000000..ed5d1cbb --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/helpers/BuildHelper.java @@ -0,0 +1,165 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.planning.helpers; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.planning.QueryProcessingException; +import tools.refinery.viatra.runtime.matchers.planning.SubPlan; +import tools.refinery.viatra.runtime.matchers.planning.SubPlanFactory; +import tools.refinery.viatra.runtime.matchers.planning.operations.PProject; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.analysis.QueryAnalyzer; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.ExportedParameter; + +/** + * @author Gabor Bergmann + * + */ +public class BuildHelper { + + private BuildHelper() { + // Hiding constructor for utility class + } + +// public static SubPlan naturalJoin(IOperationCompiler buildable, +// SubPlan primaryPlan, SubPlan secondaryPlan) { +// JoinHelper joinHelper = new JoinHelper(primaryPlan, secondaryPlan); +// return buildable.buildBetaNode(primaryPlan, secondaryPlan, joinHelper.getPrimaryMask(), +// joinHelper.getSecondaryMask(), joinHelper.getComplementerMask(), false); +// } + + + /** + * Reduces the number of tuples by trimming (existentially quantifying) the set of variables that

      + *
    • are visible in the subplan, + *
    • are not exported parameters, + *
    • have all their constraints already enforced in the subplan, + *
    and thus will not be needed anymore. + * + * @param onlyIfNotDetermined if true, no trimming performed unless there is at least one variable that is not functionally determined + * @return the plan after the trimming (possibly the original) + * @since 1.5 + */ + public static SubPlan trimUnneccessaryVariables(SubPlanFactory planFactory, /*IOperationCompiler buildable,*/ + SubPlan plan, boolean onlyIfNotDetermined, QueryAnalyzer analyzer) { + Set canBeTrimmed = new HashSet(); + Set variablesInPlan = plan.getVisibleVariables(); + for (PVariable trimCandidate : variablesInPlan) { + if (trimCandidate.getReferringConstraintsOfType(ExportedParameter.class).isEmpty()) { + if (plan.getAllEnforcedConstraints().containsAll(trimCandidate.getReferringConstraints())) + canBeTrimmed.add(trimCandidate); + } + } + final Set retainedVars = setMinus(variablesInPlan, canBeTrimmed); + if (!canBeTrimmed.isEmpty() && !(onlyIfNotDetermined && areVariablesDetermined(plan, retainedVars, canBeTrimmed, analyzer, false))) { + // TODO add smart ordering? + plan = planFactory.createSubPlan(new PProject(retainedVars), plan); + } + return plan; + } + + /** + * @return true iff a set of given variables functionally determine all visible variables in the subplan according to the subplan's constraints + * @param strict if true, only "hard" dependencies are taken into account that are strictly enforced by the model representation; + * if false, user-provided soft dependencies are included as well, that are anticipated but not guaranteed by the storage mechanism; + * use true if superfluous dependencies may taint the correctness of a computation, false if they would merely impact performance + * @since 1.5 + */ + public static boolean areAllVariablesDetermined(SubPlan plan, Collection determining, QueryAnalyzer analyzer, boolean strict) { + return areVariablesDetermined(plan, determining, plan.getVisibleVariables(), analyzer, strict); + } + + /** + * @return true iff one set of given variables functionally determine the other set according to the subplan's constraints + * @param strict if true, only "hard" dependencies are taken into account that are strictly enforced by the model representation; + * if false, user-provided soft dependencies are included as well, that are anticipated but not guaranteed by the storage mechanism; + * use true if superfluous dependencies may taint the correctness of a computation, false if they would merely impact performance + * @since 1.5 + */ + public static boolean areVariablesDetermined(SubPlan plan, Collection determining, Collection determined, + QueryAnalyzer analyzer, boolean strict) { + Map, Set> dependencies = analyzer.getFunctionalDependencies(plan.getAllEnforcedConstraints(), strict); + final Set closure = FunctionalDependencyHelper.closureOf(determining, dependencies); + final boolean isDetermined = closure.containsAll(determined); + return isDetermined; + } + + private static Set setMinus(Set a, Set b) { + Set difference = new HashSet(a); + difference.removeAll(b); + return difference; + } + + /** + * Finds an arbitrary constraint that is not enforced at the given plan. + * + * @param pSystem + * @param plan + * @return a PConstraint that is not enforced, if any, or null if all are enforced + */ + public static PConstraint getAnyUnenforcedConstraint(PBody pSystem, + SubPlan plan) { + Set allEnforcedConstraints = plan.getAllEnforcedConstraints(); + Set constraints = pSystem.getConstraints(); + for (PConstraint pConstraint : constraints) { + if (!allEnforcedConstraints.contains(pConstraint)) + return pConstraint; + } + return null; + } + + /** + * Skips the last few steps, if any, that are projections, so that a custom projection can be added instead. + * Useful for connecting body final plans into the production node. + * + * @since 2.1 + */ + public static SubPlan eliminateTrailingProjections(SubPlan plan) { + while (plan.getOperation() instanceof PProject) + plan = plan.getParentPlans().get(0); + return plan; + } + + /** + * Verifies whether all constraints are enforced and exported parameters are present. + * + * @param pSystem + * @param plan + * @throws ViatraQueryRuntimeException + */ + public static void finalCheck(final PBody pSystem, SubPlan plan, IQueryMetaContext context) { + PConstraint unenforcedConstraint = getAnyUnenforcedConstraint(pSystem, plan); + if (unenforcedConstraint != null) { + throw new QueryProcessingException( + "Pattern matcher construction terminated without successfully enforcing constraint {1}." + + " Could be caused if the value of some variables can not be deduced, e.g. by circularity of pattern constraints.", + new String[] { unenforcedConstraint.toString() }, "Could not enforce a pattern constraint", null); + } + for (ExportedParameter export : pSystem + .getConstraintsOfType(ExportedParameter.class)) { + if (!export.isReadyAt(plan, context)) { + throw new QueryProcessingException( + "Exported pattern parameter {1} could not be deduced during pattern matcher construction." + + " A pattern constraint is required to positively deduce its value.", + new String[] { export.getParameterName() }, "Could not calculate pattern parameter", + null); + } + } + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/helpers/FunctionalDependencyHelper.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/helpers/FunctionalDependencyHelper.java new file mode 100644 index 00000000..40835f52 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/helpers/FunctionalDependencyHelper.java @@ -0,0 +1,143 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Adam Dudas, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.planning.helpers; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.util.Sets; + +/** + * Helper utility class for functional dependency analysis. + * + * Throughout this class attribute sets are represented as generic sets and functional dependencies as maps from + * attribute set (generic sets) to attribute set (generic sets) + * + * @author Adam Dudas + * + */ +public class FunctionalDependencyHelper { + + private FunctionalDependencyHelper() { + // Hiding constructor for utility class + } + + /** + * Get the closure of the specified attribute set relative to the specified functional dependencies. + * + * @param attributes + * The attributes to get the closure of. + * @param dependencies + * The functional dependencies of which the closure operation is relative to. + * @return The closure of the specified attribute set relative to the specified functional dependencies. + */ + public static Set closureOf(Collection attributes, Map, Set> dependencies) { + Set closureSet = new HashSet(); + + for (Set closureSet1 = new HashSet(attributes); closureSet.addAll(closureSet1);) { + closureSet1 = new HashSet(); + for (Entry, Set> dependency : dependencies.entrySet()) { + if (closureSet.containsAll(dependency.getKey())) + closureSet1.addAll(dependency.getValue()); + } + } + + return closureSet; + } + + /** + * @return true if the dependency from the left set to the right set is trivial + * @since 1.5 + */ + public static boolean isTrivial(Set left, Set right) { + return left.containsAll(right); + } + + /*** + * Returns the dependency set over attributes in {@link targetAttributes} that are implied by a given source dependency set. + *

    Note: exponential in the size of the target attribute set. + *

    Note: minimality of the returned dependency set is currently not guaranteed. + * @param originalDependencies all dependencies that are known to hold on a wider set of attributes + * @param targetAttributes the set of attributes we are interested in + * @since 1.5 + */ + public static Map, Set> projectDependencies(Map, Set> originalDependencies, Set targetAttributes) { + // only those attributes are considered as left-hand-side candidates that occur at least once in dependencies + Set leftCandidates = new HashSet(); + for (Entry, Set> dependency : originalDependencies.entrySet()) { + if (!isTrivial(dependency.getKey(), dependency.getValue())) // only if non-trivial + leftCandidates.addAll(Sets.intersection(dependency.getKey(), targetAttributes)); + } + + // Compute an initial list of nontrivial projected dependencies - it does not have to be minimal yet + Map, Set> initialDependencies = new HashMap, Set>(); + for (Set leftSet : Sets.powerSet(leftCandidates)) { + Set rightSet = Sets.intersection(closureOf(leftSet, originalDependencies), targetAttributes); + if (!isTrivial(leftSet, rightSet)) { + initialDependencies.put(leftSet, rightSet); + } + } + // Don't forget to include constants! + Set constants = Sets.intersection(closureOf(Collections.emptySet(), originalDependencies), targetAttributes); + if (! constants.isEmpty()) { + initialDependencies.put(Collections.emptySet(), constants); + } + + // Omit those dependencies where the LHS has superfluous attributes + Map, Set> solidDependencies = new HashMap, Set>(); + for (Entry, Set> dependency : initialDependencies.entrySet()) { + Set leftSet = dependency.getKey(); + Set rightSet = dependency.getValue(); + boolean solid = true; + for (A skipped : leftSet) { // what if we skip one attribute from the left set? + Set singleton = Collections.singleton(skipped); + Set candidate = Sets.difference(leftSet, singleton); + Set rightCandidate = initialDependencies.get(candidate); + if (rightCandidate != null) { + if (Sets.union(rightCandidate, singleton).containsAll(rightSet)) { + solid = false; + break; + } + } + } + if (solid) { + solidDependencies.put(leftSet, rightSet); + } + } + + // TODO perform proper minimization, + // see e.g. page 45 in http://www.cs.ubc.ca/~hkhosrav/db/slides/03.design%20theory.pdf + + return Collections.unmodifiableMap(solidDependencies); + } + + /** + * Adds a given dependency to a mutable accumulator. + * @since 1.5 + */ + public static void includeDependency(Map, Set> accumulator, Set left, Set right) { + Set accumulatorRights = accumulator.computeIfAbsent(left, l -> new HashSet<>()); + accumulatorRights.addAll(right); + } + + /** + * Adds all given dependencies to a mutable accumulator. + * @since 1.5 + */ + public static void includeDependencies(Map, Set> accumulator, Map, Set> additionalDependencies) { + for (Entry, Set> entry : additionalDependencies.entrySet()) { + includeDependency(accumulator, entry.getKey(), entry.getValue()); + } + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/helpers/StatisticsHelper.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/helpers/StatisticsHelper.java new file mode 100644 index 00000000..b4f848a7 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/helpers/StatisticsHelper.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.planning.helpers; + +import java.util.Optional; +import java.util.function.BiFunction; + +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.Accuracy; + +/** + * Helpers dealing with optionally present statistics information + * + * @author Gabor Bergmann + * @since 2.1 + * + */ +public class StatisticsHelper { + + private StatisticsHelper() { + // Hidden utility class constructor + } + + public static Optional estimateAverageBucketSize(TupleMask groupMask, Accuracy requiredAccuracy, + BiFunction> estimateCardinality) + { + if (groupMask.isIdentity()) return Optional.of(1.0); + + Accuracy numeratorAccuracy = requiredAccuracy; + Accuracy denominatorAccuracy = requiredAccuracy.reciprocal(); + TupleMask identityMask = TupleMask.identity(groupMask.sourceWidth); + + Optional totalCountEstimate = estimateCardinality.apply(identityMask, numeratorAccuracy); + Optional bucketCountEstimate = estimateCardinality.apply(groupMask, denominatorAccuracy); + + return totalCountEstimate.flatMap(matchCount -> + bucketCountEstimate.map(bucketCount -> + bucketCount == 0L ? 0L : ((double) matchCount) / bucketCount + )); + } + + public static Optional min(Optional a, Optional b) { + if (b.isPresent()) { + if (a.isPresent()) { + return Optional.of(Math.min(a.get(), b.get())); + } else return b; + } else return a; + } + public static Optional min(Optional a, double b) { + if (a.isPresent()) { + return Optional.of(Math.min(a.get(), b)); + } else return Optional.of(b); + } + + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/helpers/TypeHelper.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/helpers/TypeHelper.java new file mode 100644 index 00000000..926a591f --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/helpers/TypeHelper.java @@ -0,0 +1,217 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.planning.helpers; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Queue; +import java.util.Set; +import java.util.stream.Collectors; + +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.psystem.ITypeInfoProviderConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.TypeJudgement; + +/** + * @author Gabor Bergmann + * @author Tamas Szabo + */ +public class TypeHelper { + + private TypeHelper() { + // Hiding constructor for utility class + } + + /** + * Collects the type constraints for the specified collection of variables. The type constraints consist of the + * constraints directly enforced on the variable itself, plus all of those that the given variable is unified with + * through equalities. + * + * @param variables + * the variables in question + * @param constraints + * the constraints in the pattern body + * @param context + * the query meta context + * @return the mapping from variable to set of type constraints + * @since 1.6 + */ + public static Map> inferUnaryTypesFor(Iterable variables, + Set constraints, IQueryMetaContext context) { + Map> typeMap = TypeHelper.inferUnaryTypes(constraints, context); + return inferUnaryTypesFor(variables, typeMap); + } + + /** + * Collects the type constraints for the specified collection of variables. The type constraints consist of the + * constraints directly enforced on the variable itself, plus all of those that the given variable is unified with + * through equalities. + * + * The method accepts a type map which is the result of the basic type inference from the + * {@link TypeHelper.inferUnaryTypes} method. The purpose of this method is that the type map can be reused across + * several calls to this method. + * + * @param variables + * the variables in question + * @param typeMap + * the type map of inference results + * @return the mapping from variable to set of type constraints + * @since 1.6 + */ + public static Map> inferUnaryTypesFor(Iterable variables, + Map> typeMap) { + Map> result = new HashMap>(); + + for (PVariable original : variables) { + // it can happen that the variable was unified into an other one due to equalities + Set keys = new HashSet(); + PVariable current = original; + + while (current != null) { + Set judgements = typeMap.get(current); + if (judgements != null) { + for (TypeJudgement judgement : judgements) { + keys.add(judgement.getInputKey()); + } + } + current = current.getDirectUnifiedInto(); + } + + result.put(original, keys); + } + + return result; + } + + /** + * Infers unary type information for variables, based on the given constraints. + * + * Subsumptions are not taken into account. + * + * @param constraints + * the set of constraints to extract type info from + */ + public static Map> inferUnaryTypes(Set constraints, + IQueryMetaContext context) { + Set equivalentJudgements = getDirectJudgements(constraints, context); + Set impliedJudgements = typeClosure(equivalentJudgements, context); + + Map> results = new HashMap>(); + for (TypeJudgement typeJudgement : impliedJudgements) { + final IInputKey inputKey = typeJudgement.getInputKey(); + if (inputKey.getArity() == 1) { + PVariable variable = (PVariable) typeJudgement.getVariablesTuple().get(0); + Set inferredTypes = results.computeIfAbsent(variable, v -> new HashSet<>()); + inferredTypes.add(typeJudgement); + } + } + return results; + } + + /** + * Gets direct judgements reported by constraints. No closure is applied yet. + */ + public static Set getDirectJudgements(Set constraints, IQueryMetaContext context) { + Set equivalentJudgements = new HashSet(); + for (PConstraint pConstraint : constraints) { + if (pConstraint instanceof ITypeInfoProviderConstraint) { + equivalentJudgements.addAll(((ITypeInfoProviderConstraint) pConstraint).getImpliedJudgements(context)); + } + } + return equivalentJudgements; + } + + /** + * Calculates the closure of a set of type judgements, with respect to supertyping. + * + * @return the set of all type judgements in typesToClose and all their direct and indirect supertypes + */ + public static Set typeClosure(Set typesToClose, IQueryMetaContext context) { + return typeClosure(Collections. emptySet(), typesToClose, context); + } + + /** + * Calculates the closure of a set of type judgements (with respect to supertyping), where the closure has been + * calculated before for a given base set, but not for a separate delta set. + *

    + * Precondition: the set (typesToClose MINUS delta) is already closed w.r.t. supertyping. + * + * @return the set of all type judgements in typesToClose and all their direct and indirect supertypes + * @since 1.6 + */ + public static Set typeClosure(Set preclosedBaseSet, Set delta, + IQueryMetaContext context) { + Queue queue = delta.stream().filter(input -> !preclosedBaseSet.contains(input)).collect(Collectors.toCollection(LinkedList::new)); + if (queue.isEmpty()) + return preclosedBaseSet; + + Set closure = new HashSet(preclosedBaseSet); + + Map> conditionalImplications = new HashMap<>(); + for (TypeJudgement typeJudgement : closure) { + conditionalImplications.putAll(typeJudgement.getConditionalImpliedJudgements(context)); + } + + do { + TypeJudgement deltaType = queue.poll(); + if (closure.add(deltaType)) { + // direct implications + queue.addAll(deltaType.getDirectlyImpliedJudgements(context)); + + // conditional implications, source key processed before, this is the condition key + final Set implicationSet = conditionalImplications.get(deltaType); + if (implicationSet != null) { + queue.addAll(implicationSet); + } + + // conditional implications, this is the source key + Map> deltaConditionalImplications = deltaType + .getConditionalImpliedJudgements(context); + for (Entry> entry : deltaConditionalImplications.entrySet()) { + if (closure.contains(entry.getKey())) { + // condition processed before + queue.addAll(entry.getValue()); + } else { + // condition not processed yet + conditionalImplications.computeIfAbsent(entry.getKey(), key -> new HashSet<>()) + .addAll(entry.getValue()); + } + } + } + } while (!queue.isEmpty()); + + return closure; + } + + /** + * Calculates a remainder set of types from a larger set, that are not subsumed by a given set of subsuming types. + * + * @param subsumableTypes + * a set of types from which some may be implied by the subsuming types + * @param subsumingTypes + * a set of types that may imply some of the subsuming types + * @return the collection of types in subsumableTypes that are NOT identical to or supertypes of any type in + * subsumingTypes. + */ + public static Set subsumeTypes(Set subsumableTypes, Set subsumingTypes, + IQueryMetaContext context) { + Set closure = typeClosure(subsumingTypes, context); + Set subsumed = new HashSet(subsumableTypes); + subsumed.removeAll(closure); + return subsumed; + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PApply.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PApply.java new file mode 100644 index 00000000..2c285b54 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PApply.java @@ -0,0 +1,94 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.planning.operations; + +import java.util.Collections; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.planning.SubPlan; +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; + +/** + * Represents a constraint application on a single parent SubPlan. + *

    Either a "selection" filter operation according to a deferred PConstraint (or transform in case of eval/aggregate), or + * alternatively a shorthand for PJoin + a PEnumerate on the right input for an enumerable PConstraint. + * + *

    WARNING: if there are coinciding variables in the variable tuple of the enumerable constraint, + * it is the responsibility of the compiler to check them for equality. + * + * @author Bergmann Gabor + * + */ +public class PApply extends POperation { + + private PConstraint pConstraint; + + public PApply(PConstraint pConstraint) { + super(); + this.pConstraint = pConstraint; + } + public PConstraint getPConstraint() { + return pConstraint; + } + + @Override + public String getShortName() { + return String.format("APPLY_%s", pConstraint.toString()); + } + + @Override + public Set getDeltaConstraints() { + return Collections.singleton(pConstraint); + } + + @Override + public int numParentSubPlans() { + return 1; + } + + @Override + public void checkConsistency(SubPlan subPlan) { + super.checkConsistency(subPlan); + for (SubPlan parentPlan : subPlan.getParentPlans()) + Preconditions.checkArgument(!parentPlan.getAllEnforcedConstraints().contains(pConstraint), + "Double-checking constraint %s", pConstraint); + // TODO obtain context? + //if (pConstraint instanceof DeferredPConstraint) + // Preconditions.checkArgument(((DeferredPConstraint) pConstraint).isReadyAt(subPlan, context)) + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime + * result + + ((pConstraint == null) ? 0 : pConstraint + .hashCode()); + return result; + } + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof PApply)) + return false; + PApply other = (PApply) obj; + if (pConstraint == null) { + if (other.pConstraint != null) + return false; + } else if (!pConstraint.equals(other.pConstraint)) + return false; + return true; + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PEnumerate.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PEnumerate.java new file mode 100644 index 00000000..a975d50c --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PEnumerate.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.planning.operations; + +import java.util.Collections; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.psystem.EnumerablePConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; + +/** + * Represents a base relation defined by the instance set of an enumerable PConstraint; there are no parent SubPlans. + * + *

    WARNING: if there are coinciding variables in the variable tuple of the enumerable constraint, + * it is the responsibility of the compiler to check them for equality. + * @author Bergmann Gabor + * + */ +public class PEnumerate extends POperation { + + EnumerablePConstraint enumerablePConstraint; + + public PEnumerate(EnumerablePConstraint enumerablePConstraint) { + super(); + this.enumerablePConstraint = enumerablePConstraint; + } + public EnumerablePConstraint getEnumerablePConstraint() { + return enumerablePConstraint; + } + + @Override + public Set getDeltaConstraints() { + return Collections.singleton(enumerablePConstraint); + } + @Override + public int numParentSubPlans() { + return 0; + } + @Override + public String getShortName() { + return enumerablePConstraint.toString(); + } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime + * result + + ((enumerablePConstraint == null) ? 0 : enumerablePConstraint + .hashCode()); + return result; + } + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof PEnumerate)) + return false; + PEnumerate other = (PEnumerate) obj; + if (enumerablePConstraint == null) { + if (other.enumerablePConstraint != null) + return false; + } else if (!enumerablePConstraint.equals(other.enumerablePConstraint)) + return false; + return true; + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PJoin.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PJoin.java new file mode 100644 index 00000000..10e0a85a --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PJoin.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.planning.operations; + +import java.util.Collections; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; + +/** + * Represents a natural join of two parent SubPlans. + * @author Bergmann Gabor + * + */ +public class PJoin extends POperation { + +// // TODO leave here? is this a problem in equivalnece checking? +// private Set onVariables; + + public PJoin(/*Set onVariables*/) { + super(); + //this.onVariables = new HashSet(onVariables); + } +// public Set getOnVariables() { +// return onVariables; +// } + + @Override + public Set getDeltaConstraints() { + return Collections.emptySet(); + } + @Override + public int numParentSubPlans() { + return 2; + } + + @Override + public String getShortName() { + return "JOIN"; //String.format("JOIN_{%s}", Joiner.on(",").join(onVariables)); + } + + @Override + public int hashCode() { + return getClass().hashCode(); + } + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof PJoin)) + return false; + return true; + } + + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/POperation.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/POperation.java new file mode 100644 index 00000000..e71cf217 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/POperation.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.planning.operations; + +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.planning.SubPlan; +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; + +/** + * Abstract superclass for representing a high-level query evaluation operation. + * + *

    Subclasses correspond to various POperations modeled after relational algebra. + * + * @author Bergmann Gabor + * + */ +public abstract class POperation { + + /** + * Newly enforced constraints + */ + public abstract Set getDeltaConstraints(); + + public abstract String getShortName(); + + /** + * @return the number of SubPlans that must be specified as parents + */ + public abstract int numParentSubPlans(); + + /** + * Checks whether this constraint can be properly applied at the given SubPlan. + */ + public void checkConsistency(SubPlan subPlan) { + Preconditions.checkArgument(this == subPlan.getOperation(), "POperation misalignment"); + Preconditions.checkArgument(subPlan.getParentPlans().size() == numParentSubPlans(), "Incorrect number of parent SubPlans"); + } + + @Override + public String toString() { + return getShortName(); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PProject.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PProject.java new file mode 100644 index 00000000..d0539b2c --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PProject.java @@ -0,0 +1,109 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.planning.operations; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import tools.refinery.viatra.runtime.matchers.planning.SubPlan; +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; + +/** + * Represents a projection of a single parent SubPlan onto a limited set of variables. + *

    May optionally prescribe an ordering of variables (List, as opposed to Set). + * + * @author Bergmann Gabor + * + */ +public class PProject extends POperation { + + private Collection toVariables; + private boolean ordered; + + + public PProject(Set toVariables) { + super(); + this.toVariables = toVariables; + this.ordered = false; + } + public PProject(List toVariables) { + super(); + this.toVariables = toVariables; + this.ordered = true; + } + + public Collection getToVariables() { + return toVariables; + } + public boolean isOrdered() { + return ordered; + } + + @Override + public Set getDeltaConstraints() { + return Collections.emptySet(); + } + @Override + public int numParentSubPlans() { + return 1; + } + @Override + public void checkConsistency(SubPlan subPlan) { + super.checkConsistency(subPlan); + final SubPlan parentPlan = subPlan.getParentPlans().get(0); + + Preconditions.checkArgument(parentPlan.getVisibleVariables().containsAll(toVariables), + () -> toVariables.stream() + .filter(input -> !parentPlan.getVisibleVariables().contains(input)).map(PVariable::getName) + .collect(Collectors.joining(",", "Variables missing from project: ", ""))); + } + + @Override + public String getShortName() { + return String.format("PROJECT%s_{%s}", ordered? "!" : "", + toVariables.stream().map(PVariable::getName).collect(Collectors.joining(","))); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (ordered ? 1231 : 1237); + result = prime * result + + ((toVariables == null) ? 0 : toVariables.hashCode()); + return result; + } + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof PProject)) + return false; + PProject other = (PProject) obj; + if (ordered != other.ordered) + return false; + if (toVariables == null) { + if (other.toVariables != null) + return false; + } else if (!toVariables.equals(other.toVariables)) + return false; + return true; + } + + + + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PStart.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PStart.java new file mode 100644 index 00000000..9e6ea10e --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/planning/operations/PStart.java @@ -0,0 +1,90 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.planning.operations; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; + +/** + * No constraints, and no parent SubPlan, just a (possibly empty) set of a priori known (input) variables. Satisfied by a single tuple. + * + *

    Can also be used without a priori variables, + * e.g. as a "virtual parent" in extreme cases, + * such as pattern foo(Bar) = {Bar = eval (3*4)} + * + * @author Bergmann Gabor + * + */ +public class PStart extends POperation { + + private Set aPrioriVariables; + + + public PStart(Set aPrioriVariables) { + super(); + this.aPrioriVariables = aPrioriVariables; + } + public PStart(PVariable... aPrioriVariables) { + this(new HashSet(Arrays.asList(aPrioriVariables))); + } + public Set getAPrioriVariables() { + return aPrioriVariables; + } + + @Override + public String getShortName() { + return aPrioriVariables.stream().map(PVariable::getName).collect(Collectors.joining(",", "START_{", "}")); + } + @Override + public int numParentSubPlans() { + return 0; + } + + @Override + public Set getDeltaConstraints() { + return Collections.emptySet(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime + * result + + ((aPrioriVariables == null) ? 0 : aPrioriVariables.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof PStart)) + return false; + PStart other = (PStart) obj; + if (aPrioriVariables == null) { + if (other.aPrioriVariables != null) + return false; + } else if (!aPrioriVariables.equals(other.aPrioriVariables)) + return false; + return true; + } + + + + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/BasePConstraint.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/BasePConstraint.java new file mode 100644 index 00000000..eda4aa25 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/BasePConstraint.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.psystem; + +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author Gabor Bergmann + * + */ +public abstract class BasePConstraint implements PConstraint { + + + protected PBody pBody; + private final Set affectedVariables; + + + private final int sequentialID = nextID.getAndIncrement(); + + // Use a static atomic integer to avoid race conditions when creating new constraints. + private static AtomicInteger nextID = new AtomicInteger(0); + + public BasePConstraint(PBody pBody, Set affectedVariables) { + super(); + this.pBody = pBody; + this.affectedVariables = new HashSet(affectedVariables); + + for (PVariable pVariable : affectedVariables) { + pVariable.refer(this); + } + pBody.registerConstraint(this); + } + + @Override + public String toString() { + return "PC[" + getClass().getSimpleName() + ":" + toStringRest() + "]"; + } + + protected abstract String toStringRest(); + + @Override + public Set getAffectedVariables() { + return affectedVariables; + } + + @Override + public Map, Set> getFunctionalDependencies(IQueryMetaContext context) { + return Collections.emptyMap(); + } + + @Override + public void replaceVariable(PVariable obsolete, PVariable replacement) { + pBody.checkMutability(); + if (affectedVariables.remove(obsolete)) { + affectedVariables.add(replacement); + obsolete.unrefer(this); + replacement.refer(this); + doReplaceVariable(obsolete, replacement); + } + } + + protected abstract void doReplaceVariable(PVariable obsolete, PVariable replacement); + + @Override + public void delete() { + pBody.checkMutability(); + for (PVariable pVariable : affectedVariables) { + pVariable.unrefer(this); + } + pBody.unregisterConstraint(this); + } + + @Override + public void checkSanity() { + } + + /** + * For backwards compatibility. Equivalent to {@link #getBody()} + */ + public PBody getPSystem() { + return pBody; + } + /** + * @since 2.1 + */ + @Override + public PBody getBody() { + return pBody; + } + + @Override + public int getMonotonousID() { + return sequentialID; + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/DeferredPConstraint.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/DeferredPConstraint.java new file mode 100644 index 00000000..d2bf088c --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/DeferredPConstraint.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.psystem; + +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.planning.SubPlan; + +/** + * Any constraint that can only be checked on certain SubPlans (e.g. those plans that already contain some variables). + * + * @author Gabor Bergmann + * + */ +public abstract class DeferredPConstraint extends BasePConstraint { + + public DeferredPConstraint(PBody pBody, Set affectedVariables) { + super(pBody, affectedVariables); + } + + public abstract boolean isReadyAt(SubPlan plan, IQueryMetaContext context); + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/EnumerablePConstraint.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/EnumerablePConstraint.java new file mode 100644 index 00000000..9129aa47 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/EnumerablePConstraint.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.psystem; + +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; + +/** + * A constraint for which all satisfying tuples of variable values can be enumerated at any point during run-time. + * + * @author Gabor Bergmann + * + */ +public abstract class EnumerablePConstraint extends BasePConstraint { + protected Tuple variablesTuple; + + protected EnumerablePConstraint(PBody pBody, Tuple variablesTuple) { + super(pBody, variablesTuple. getDistinctElements()); + this.variablesTuple = variablesTuple; + } + + @Override + public void doReplaceVariable(PVariable obsolete, PVariable replacement) { + variablesTuple = variablesTuple.replaceAll(obsolete, replacement); + } + + @Override + protected String toStringRest() { + String stringRestRest = toStringRestRest(); + String tupleString = "@" + variablesTuple.toString(); + return stringRestRest == null ? tupleString : ":" + stringRestRest + tupleString; + } + + protected String toStringRestRest() { + return null; + } + + public Tuple getVariablesTuple() { + return variablesTuple; + } + + @Override + public Set getDeducedVariables() { + return getAffectedVariables(); + } + + public PVariable getVariableInTuple(int index) { + return (PVariable) this.variablesTuple.get(index); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IExpressionEvaluator.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IExpressionEvaluator.java new file mode 100644 index 00000000..686999f7 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IExpressionEvaluator.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem; + +/** + * An expression evaluator is used to execute arbitrary Java code during pattern matching. In order to include the + * evaluation in the planning seemlessly it is expected from the evaluator implementors to report all used PVariables by + * name. + * + * @author Zoltan Ujhelyi + * + */ +public interface IExpressionEvaluator { + + /** + * A textual description of the expression. Used only for debug purposes, but must not be null. + */ + String getShortDescription(); + + /** + * All input parameter names should be reported correctly. + */ + Iterable getInputParameterNames(); + + /** + * The expression evaluator code + * + * @param provider + * the value provider is an engine-specific way of reading internal variable tuples to evaluate the + * expression with + * @return the result of the expression: in case of predicate evaluation the return value must be true or false; + * otherwise the result can be an arbitrary object. No null values should be returned. + * @throws Exception + */ + Object evaluateExpression(IValueProvider provider) throws Exception; +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IMultiQueryReference.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IMultiQueryReference.java new file mode 100644 index 00000000..8f647c64 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IMultiQueryReference.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2010-2022, Tamas Szabo, GitHub + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem; + +import java.util.Collection; + +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; + +/** + * A {@link PConstraint} that implements this interface refers to a list of PQueries. + * + * @author Tamas Szabo + * @since 2.8 + * + */ +public interface IMultiQueryReference { + + Collection getReferredQueries(); + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IQueryReference.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IQueryReference.java new file mode 100644 index 00000000..9ee05b39 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IQueryReference.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem; + +import java.util.Collections; +import java.util.List; + +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; + +/** + * A {@link PConstraint} that implements this interface refers to a {@link PQuery}. + * + * @author Zoltan Ujhelyi + * + */ +public interface IQueryReference extends IMultiQueryReference { + + PQuery getReferredQuery(); + + /** + * @since 2.8 + */ + @Override + default List getReferredQueries() { + return Collections.singletonList(getReferredQuery()); + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IRelationEvaluator.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IRelationEvaluator.java new file mode 100644 index 00000000..e4c396d8 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IRelationEvaluator.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2010-2022, Tamas Szabo, GitHub + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem; + +import java.util.List; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; + +/** + * Implementations of this interface take an arbitrary number of input relations with their contents and compute the + * tuples of a single output relation. + * + * @author Tamas Szabo + * @since 2.8 + * + */ +public interface IRelationEvaluator { + + /** + * A textual description of the evaluator. Used only for debug purposes, but must not be null. + */ + String getShortDescription(); + + /** + * The relation evaluator code. For performance reasons, it is expected that the returned set is a mutable + * collection, and the caller must be allowed to actually perform mutations! + */ + Set evaluateRelation(List> inputs) throws Exception; + + /** + * For each input relation that this evaluator requires, this method returns the expected arities of the relations in order. + */ + List getInputArities(); + + /** + * Returns the arity of the output relation that this evaluator computes. + */ + int getOutputArity(); + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/ITypeConstraint.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/ITypeConstraint.java new file mode 100644 index 00000000..b72035a8 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/ITypeConstraint.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; + +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; + +import java.util.Set; + +/** + * Common superinterface of enumerable and deferred type constraints. + * @author Bergmann Gabor + * + */ +public interface ITypeConstraint extends ITypeInfoProviderConstraint { + + public abstract TypeJudgement getEquivalentJudgement(); + + /** + * Static internal utility class for implementations of {@link ITypeConstraint}s. + * @author Bergmann Gabor + */ + public static class TypeConstraintUtil { + + private TypeConstraintUtil() { + // Hiding constructor for utility class + } + + public static Map, Set> getFunctionalDependencies(IQueryMetaContext context, IInputKey inputKey, Tuple variablesTuple) { + final Map, Set> result = new HashMap, Set>(); + + Set, Set>> dependencies = context.getFunctionalDependencies(inputKey).entrySet(); + for (Entry, Set> dependency : dependencies) { + result.put( + transcribeVariables(dependency.getKey(), variablesTuple), + transcribeVariables(dependency.getValue(), variablesTuple) + ); + } + + return result; + } + + private static Set transcribeVariables(Set indices, Tuple variablesTuple) { + Set result = new HashSet(); + for (Integer index : indices) { + result.add((PVariable) variablesTuple.get(index)); + } + return result; + } + + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/ITypeInfoProviderConstraint.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/ITypeInfoProviderConstraint.java new file mode 100644 index 00000000..ff127d38 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/ITypeInfoProviderConstraint.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.psystem; + +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; + +/** + * @author Gabor Bergmann + * + */ +public interface ITypeInfoProviderConstraint extends PConstraint { + + /** + * Returns type information implied by this constraint. + * + */ + public Set getImpliedJudgements(IQueryMetaContext context); + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IValueProvider.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IValueProvider.java new file mode 100644 index 00000000..d959adc4 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/IValueProvider.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem; + +/** + * Helper interface to get values from a tuple of variables. All pattern matching engines are expected to implement this + * to handle their internal structures. + * + * @author Zoltan Ujhelyi + * + */ +public interface IValueProvider { + + /** + * Returns the value of the selected variable + * @param variableName + * @return the value of the variable; never null + * @throws IllegalArgumentException if the variable is not defined + */ + Object getValue(String variableName); +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/InitializablePQuery.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/InitializablePQuery.java new file mode 100644 index 00000000..a82d12ec --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/InitializablePQuery.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem; + +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; +import tools.refinery.viatra.runtime.matchers.psystem.annotations.PAnnotation; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PProblem; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; + +/** + * Adds extra methods to the PQuery interface to initialize its contents. + * + * @author Zoltan Ujhelyi + * + */ +public interface InitializablePQuery extends PQuery { + + /** + * Sets the query status. Only applicable if the pattern is still {@link PQueryStatus#UNINITIALIZED uninitialized}. + * + * @param status the new status + */ + void setStatus(PQueryStatus status); + + /** + * Adds a detected error. Only applicable if the pattern is still {@link PQueryStatus#UNINITIALIZED uninitialized}. + * + * @param problem the new problem + */ + void addError(PProblem problem); + + /** + * Sets up the bodies of the pattern. Only applicable if the pattern is still {@link PQueryStatus#UNINITIALIZED + * uninitialized}. + * + * @param bodies + * @throws ViatraQueryRuntimeException + */ + void initializeBodies(Set bodies); + + /** + * Adds an annotation to the specification. Only applicable if the pattern is still + * {@link PQueryStatus#UNINITIALIZED uninitialized}. + * + * @param annotation + */ + void addAnnotation(PAnnotation annotation); +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/KeyedEnumerablePConstraint.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/KeyedEnumerablePConstraint.java new file mode 100644 index 00000000..91eea817 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/KeyedEnumerablePConstraint.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.psystem; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; + +/** + * @author Gabor Bergmann + * + */ +public abstract class KeyedEnumerablePConstraint extends EnumerablePConstraint { + + protected KeyType supplierKey; + + public KeyedEnumerablePConstraint(PBody pBody, Tuple variablesTuple, + KeyType supplierKey) { + super(pBody, variablesTuple); + this.supplierKey = supplierKey; + } + + @Override + protected String toStringRestRest() { + return supplierKey == null ? "$any(null)" : keyToString(); + } + + protected abstract String keyToString(); + + public KeyType getSupplierKey() { + return supplierKey; + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/PBody.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/PBody.java new file mode 100644 index 00000000..c38dc23a --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/PBody.java @@ -0,0 +1,289 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.psystem; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.stream.Collectors; + +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.planning.helpers.TypeHelper; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.ExportedParameter; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.ConstantValue; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PDisjunction; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery.PQueryStatus; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; + +/** + * A set of constraints representing a pattern body + * + * @author Gabor Bergmann + * + */ +public class PBody implements PTraceable { + + public static final String VIRTUAL_VARIABLE_PREFIX = ".virtual"; + private static final String VIRTUAL_VARIABLE_PATTERN = VIRTUAL_VARIABLE_PREFIX + "{%d}"; + + private PQuery query; + + /** + * If null, then parent query status is reused + */ + private PQueryStatus status = PQueryStatus.UNINITIALIZED; + + private Set allVariables; + private Set uniqueVariables; + private List symbolicParameters; + private Map variablesByName; + private Set constraints; + private int nextVirtualNodeID; + private PDisjunction containerDisjunction; + + public PBody(PQuery query) { + super(); + this.query = query; + allVariables = new LinkedHashSet<>(); + uniqueVariables = new LinkedHashSet<>(); + variablesByName = new HashMap<>(); + constraints = new LinkedHashSet<>(); + } + + /** + * @return whether the submission of the new variable was successful + */ + private boolean addVariable(PVariable var) { + checkMutability(); + Object name = var.getName(); + if (!variablesByName.containsKey(name)) { + allVariables.add(var); + if (var.isUnique()) + uniqueVariables.add(var); + variablesByName.put(name, var); + return true; + } else { + return false; + } + } + + /** + * Use this method to add a newly created constraint to the pSystem. + * + * @return whether the submission of the new constraint was successful + */ + boolean registerConstraint(PConstraint constraint) { + checkMutability(); + return constraints.add(constraint); + } + + /** + * Use this method to remove an obsolete constraint from the pSystem. + * + * @return whether the removal of the constraint was successful + */ + boolean unregisterConstraint(PConstraint constraint) { + checkMutability(); + return constraints.remove(constraint); + } + + @SuppressWarnings("unchecked") + public Set getConstraintsOfType(Class constraintClass) { + Set result = new HashSet(); + for (PConstraint pConstraint : constraints) { + if (constraintClass.isInstance(pConstraint)) + result.add((ConstraintType) pConstraint); + } + return result; + } + + public PVariable newVirtualVariable() { + checkMutability(); + String name; + do { + + name = String.format(VIRTUAL_VARIABLE_PATTERN, nextVirtualNodeID++); + } while (variablesByName.containsKey(name)); + PVariable var = new PVariable(this, name, true); + addVariable(var); + return var; + } + + public PVariable newVirtualVariable(String name) { + checkMutability(); + Preconditions.checkArgument(!variablesByName.containsKey(name), "ID %s already used for a virtual variable", name); + PVariable var = new PVariable(this, name, true); + addVariable(var); + return var; + } + + public PVariable newConstantVariable(Object value) { + checkMutability(); + PVariable virtual = newVirtualVariable(); + new ConstantValue(this, virtual, value); + return virtual; + } + + public Set getAllVariables() { + return allVariables; + } + + public Set getUniqueVariables() { + return uniqueVariables; + } + + private PVariable getVariableByName(Object name) { + return variablesByName.get(name).getUnifiedIntoRoot(); + } + + /** + * Find a PVariable by name + * + * @param name + * @return the found variable + * @throws IllegalArgumentException + * if no PVariable is found with the selected name + */ + public PVariable getVariableByNameChecked(Object name) { + if (!variablesByName.containsKey(name)) + throw new IllegalArgumentException(String.format("Cannot find PVariable %s", name)); + return getVariableByName(name); + } + + /** + * Finds and returns a PVariable by name. If no PVariable exists with the name in the body, a new one is created. If + * the name of the variable starts with {@value #VIRTUAL_VARIABLE_PREFIX}, the created variable will be considered + * virtual. + * + * @param name + * @return a PVariable with the selected name; never null + */ + public PVariable getOrCreateVariableByName(String name) { + checkMutability(); + if (!variablesByName.containsKey(name)) { + addVariable(new PVariable(this, name, name.startsWith(VIRTUAL_VARIABLE_PREFIX))); + } + return getVariableByName(name); + } + + public Set getConstraints() { + return constraints; + } + + public PQuery getPattern() { + return query; + } + + void noLongerUnique(PVariable pVariable) { + assert (!pVariable.isUnique()); + uniqueVariables.remove(pVariable); + } + + /** + * Returns the symbolic parameters of the body.

    + * + *

    + * Warning: if two PVariables are unified, the returned list changes. If you want to have a stable + * version, consider using {@link #getSymbolicParameters()}. + * + * @return a non-null, but possibly empty list + */ + public List getSymbolicParameterVariables() { + return getSymbolicParameters().stream().map(ExportedParameter::getParameterVariable) + .collect(Collectors.toList()); + } + + /** + * Returns the exported parameter constraints of the body. + * + * @return a non-null, but possibly empty list + */ + public List getSymbolicParameters() { + if (symbolicParameters == null) + symbolicParameters = new ArrayList<>(); + return symbolicParameters; + } + + /** + * Sets the exported parameter constraints of the body, if this instance is mutable. + * @param symbolicParameters the new value + */ + public void setSymbolicParameters(List symbolicParameters) { + checkMutability(); + this.symbolicParameters = new ArrayList<>(symbolicParameters); + } + + /** + * Sets a specific status for the body. If set, the parent PQuery status will not be checked; if set to null, its corresponding PQuery + * status is checked for mutability. + * + * @param status + * the status to set + */ + public void setStatus(PQueryStatus status) { + this.status = status; + } + + public boolean isMutable() { + if (status == null) { + return query.isMutable(); + } else { + return status.equals(PQueryStatus.UNINITIALIZED); + } + } + + void checkMutability() { + if (status == null) { + query.checkMutability(); + } else { + Preconditions.checkState(status.equals(PQueryStatus.UNINITIALIZED), "Initialized queries are not mutable"); + } + } + + /** + * Returns the disjunction the body is contained with. This disjunction may either be the + * {@link PQuery#getDisjunctBodies() canonical disjunction of the corresponding query} or something equivalent. + * + * @return the container disjunction of the body. Can be null if body is not in a disjunction yet. + */ + public PDisjunction getContainerDisjunction() { + return containerDisjunction; + } + + /** + * @param containerDisjunction the containerDisjunction to set + */ + public void setContainerDisjunction(PDisjunction containerDisjunction) { + Preconditions.checkArgument(query.equals(containerDisjunction.getQuery()), "Disjunction of pattern %s incompatible with body %s", containerDisjunction.getQuery().getFullyQualifiedName(), query.getFullyQualifiedName()); + Preconditions.checkState(this.containerDisjunction == null, "Disjunction is already set."); + this.containerDisjunction = containerDisjunction; + } + + /** + * All unary input keys directly prescribed by constraints, grouped by variable. + *

    to supertype inference or subsumption applied at this point. + */ + public Map> getAllUnaryTypeRestrictions(IQueryMetaContext context) { + Map> currentRestrictions = allUnaryTypeRestrictions.get(context); + if (currentRestrictions == null) { + currentRestrictions = TypeHelper.inferUnaryTypes(getConstraints(), context); + allUnaryTypeRestrictions.put(context, currentRestrictions); + } + return currentRestrictions; + } + private WeakHashMap>> allUnaryTypeRestrictions = new WeakHashMap>>(); + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/PConstraint.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/PConstraint.java new file mode 100644 index 00000000..ae2c4632 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/PConstraint.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.psystem; + +import java.util.Comparator; +import java.util.Map; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.psystem.analysis.QueryAnalyzer; + +/** + * @author Gabor Bergmann + * + */ +public interface PConstraint extends PTraceable { + + /** + * @since 2.1 + * @return the query body this constraint belongs to + */ + public PBody getBody(); + + /** + * All variables affected by this constraint. + */ + public Set getAffectedVariables(); + + /** + * The set of variables whose potential values can be enumerated (once all non-deduced variables have known values). + */ + public Set getDeducedVariables(); + + /** + * A (preferably minimal) cover of known functional dependencies between variables. + * @noreference Use {@link QueryAnalyzer} instead to properly handle dependencies of pattern calls. + * @return non-trivial functional dependencies in the form of {variables} --> {variables}, where dependencies with the same lhs are unified. + */ + public Map,Set> getFunctionalDependencies(IQueryMetaContext context); + + public void replaceVariable(PVariable obsolete, PVariable replacement); + + public void delete(); + + public void checkSanity(); + + /** + * Returns an integer ID that is guaranteed to increase strictly monotonously for constraints within a pBody. + */ + public abstract int getMonotonousID(); + + + /** + * A comparator that orders constraints by their {@link #getMonotonousID() monotonous identifiers}. Should only used + * for tiebreaking in other comparators. + * + * @since 2.0 + */ + public static final Comparator COMPARE_BY_MONOTONOUS_ID = (arg0, arg1) -> arg0.getMonotonousID() - arg1.getMonotonousID(); + + + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/PTraceable.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/PTraceable.java new file mode 100644 index 00000000..f0241a9c --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/PTraceable.java @@ -0,0 +1,16 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Dénes Harmath, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem; + +/** + * Marker interface for PSystem elements that can be traced. + * @since 1.6 + */ +public interface PTraceable { +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/PVariable.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/PVariable.java new file mode 100644 index 00000000..b6ea4861 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/PVariable.java @@ -0,0 +1,203 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.psystem; + +import java.util.HashSet; +import java.util.Set; + +/** + * @author Gabor Bergmann + * + */ +public class PVariable { + private PBody pBody; + /** + * The name of the pattern variable. This is the unique key of the pattern node. + */ + private String name; + /** + * virtual pVariables are nodes that do not correspond to actual pattern variables; they represent constants or Term + * substitutes + */ + private boolean virtual; + + /** + * Set of constraints that mention this variable + */ + private Set referringConstraints; + + /** + * Determines whether there are any constraints that can deduce this variable + */ + private Boolean deducable; + + /** + * Another PVariable this variable has been unified into. Please use the other variable instead of this. Null iff + * this is still a first-class variable. + */ + private PVariable unifiedInto; + + PVariable(PBody pBody, String name) { + this(pBody, name, false); + } + + PVariable(PBody pBody, String name, boolean virtual) { + super(); + this.pBody = pBody; + this.name = name; + this.virtual = virtual; + // this.exportedParameter = false; + this.referringConstraints = new HashSet(); + this.unifiedInto = null; + this.deducable = false; + } + + /** + * Replaces this variable with a given other, resulting in their unification. This variable will no longer be + * unique. + * + * @param replacement + */ + public void unifyInto(PVariable replacement) { + pBody.checkMutability(); + replacementCheck(); + replacement = replacement.getUnifiedIntoRoot(); + + if (this.equals(replacement)) + return; + + if (!this.isVirtual() && replacement.isVirtual()) { + replacement.unifyInto(this); + } else { + // replacement.referringConstraints.addAll(this.referringConstraints); + // replacement.exportedParameter |= this.exportedParameter; + replacement.virtual &= this.virtual; + if (replacement.deducable != null && this.deducable != null) + replacement.deducable |= this.deducable; + else + replacement.deducable = null; + Set snapshotConstraints = // avoid ConcurrentModificationX + new HashSet(this.referringConstraints); + for (PConstraint constraint : snapshotConstraints) { + constraint.replaceVariable(this, replacement); + } + // replacementCheck() will fail from this point + this.unifiedInto = replacement; + pBody.noLongerUnique(this); + } + } + + /** + * Determines whether there are any constraints that can deduce this variable + */ + public boolean isDeducable() { + replacementCheck(); + if (deducable == null) { + deducable = false; + for (PConstraint pConstraint : getReferringConstraints()) { + if (pConstraint.getDeducedVariables().contains(this)) { + deducable = true; + break; + } + } + } + return deducable; + } + + /** + * Register that this variable is referred by the given constraint. + * + * @param constraint + */ + public void refer(PConstraint constraint) { + pBody.checkMutability(); + replacementCheck(); + deducable = null; + referringConstraints.add(constraint); + } + + /** + * Register that this variable is no longer referred by the given constraint. + * + * @param constraint + */ + public void unrefer(PConstraint constraint) { + pBody.checkMutability(); + replacementCheck(); + deducable = null; + referringConstraints.remove(constraint); + } + + /** + * @return the name of the pattern variable. This is the unique key of the pattern node. + */ + public String getName() { + replacementCheck(); + return name; + } + + /** + * @return the virtual + */ + public boolean isVirtual() { + replacementCheck(); + return virtual; + } + + /** + * @return the referringConstraints + */ + public Set getReferringConstraints() { + replacementCheck(); + return referringConstraints; + } + + @SuppressWarnings("unchecked") + public Set getReferringConstraintsOfType(Class constraintClass) { + replacementCheck(); + Set result = new HashSet(); + for (PConstraint pConstraint : referringConstraints) { + if (constraintClass.isInstance(pConstraint)) + result.add((ConstraintType) pConstraint); + } + return result; + } + + @Override + public String toString() { + // replacementCheck(); + return name;// + ":PatternNode"; + } + + public PVariable getDirectUnifiedInto() { + return unifiedInto; + } + + public PVariable getUnifiedIntoRoot() { + PVariable nextUnified = unifiedInto; + PVariable oldUnifiedInto = this; + while (nextUnified != null) { + oldUnifiedInto = nextUnified; + nextUnified = oldUnifiedInto.getDirectUnifiedInto(); + } + return oldUnifiedInto; // unifiedInto; + } + + public boolean isUnique() { + return unifiedInto == null; + } + + private void replacementCheck() { + if (unifiedInto != null) + throw new IllegalStateException("Illegal usage of variable " + name + " which has been replaced with " + + unifiedInto.name); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/TypeJudgement.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/TypeJudgement.java new file mode 100644 index 00000000..4447b225 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/TypeJudgement.java @@ -0,0 +1,153 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.context.InputKeyImplication; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.TypeFilterConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.TypeConstraint; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; + +/** + * A judgement that means that the given tuple of variables will represent a tuple of values that is a member of the extensional relation identified by the given input key. + * @author Bergmann Gabor + * + */ +public class TypeJudgement { + + private IInputKey inputKey; + private Tuple variablesTuple; + /** + * @param inputKey + * @param variablesTuple + */ + public TypeJudgement(IInputKey inputKey, Tuple variablesTuple) { + super(); + this.inputKey = inputKey; + this.variablesTuple = variablesTuple; + } + public IInputKey getInputKey() { + return inputKey; + } + public Tuple getVariablesTuple() { + return variablesTuple; + } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + + ((inputKey == null) ? 0 : inputKey.hashCode()); + result = prime * result + + ((variablesTuple == null) ? 0 : variablesTuple.hashCode()); + return result; + } + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof TypeJudgement)) + return false; + TypeJudgement other = (TypeJudgement) obj; + if (inputKey == null) { + if (other.inputKey != null) + return false; + } else if (!inputKey.equals(other.inputKey)) + return false; + if (variablesTuple == null) { + if (other.variablesTuple != null) + return false; + } else if (!variablesTuple.equals(other.variablesTuple)) + return false; + return true; + } + + public Set getDirectlyImpliedJudgements(IQueryMetaContext context) { + Set results = new HashSet(); + results.add(this); + + Collection implications = context.getImplications(this.inputKey); + for (InputKeyImplication inputKeyImplication : implications) { + results.add( + transcribeImplication(inputKeyImplication) + ); + } + + return results; + } + + /** + * @since 1.6 + */ + public Set getWeakenedAlternativeJudgements(IQueryMetaContext context) { + Set results = new HashSet(); + + Collection implications = context.getWeakenedAlternatives(this.inputKey); + for (InputKeyImplication inputKeyImplication : implications) { + results.add( + transcribeImplication(inputKeyImplication) + ); + } + + return results; + } + + /** + * @since 2.0 + */ + public Map> getConditionalImpliedJudgements(IQueryMetaContext context) { + return context.getConditionalImplications(this.inputKey).entrySet().stream().collect(Collectors.toMap( + entry -> transcribeImplication(entry.getKey()), + entry -> entry.getValue().stream().map(this::transcribeImplication).collect(Collectors.toSet()))); + } + + + + private TypeJudgement transcribeImplication(InputKeyImplication inputKeyImplication) { + return new TypeJudgement( + inputKeyImplication.getImpliedKey(), + transcribeVariablesToTuple(inputKeyImplication.getImpliedIndices()) + ); + } + private Tuple transcribeVariablesToTuple(List indices) { + Object[] elements = new Object[indices.size()]; + for (int i = 0; i < indices.size(); ++i) + elements[i] = variablesTuple.get(indices.get(i)); + return Tuples.flatTupleOf(elements); + } + + @Override + public String toString() { + return "TypeJudgement:" + inputKey.getPrettyPrintableName() + "@" + variablesTuple.toString(); + } + + /** + * Creates this judgement as a direct type constraint in the given PBody under construction. + *

    pre: the variables tuple must be formed of variables of that PBody. + * @since 1.6 + */ + public PConstraint createConstraintFor(PBody pBody) { + if (inputKey.isEnumerable()) { + return new TypeConstraint(pBody, variablesTuple, inputKey); + } else { + return new TypeFilterConstraint(pBody, variablesTuple, inputKey); + } + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/VariableDeferredPConstraint.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/VariableDeferredPConstraint.java new file mode 100644 index 00000000..8ea6bb93 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/VariableDeferredPConstraint.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.psystem; + +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.planning.SubPlan; + +/** + * A kind of deferred constraint that can only be checked when a set of deferring variables are all present in a plan. + * + * @author Gabor Bergmann + * + */ +public abstract class VariableDeferredPConstraint extends DeferredPConstraint { + + public VariableDeferredPConstraint(PBody pBody, + Set affectedVariables) { + super(pBody, affectedVariables); + } + + public abstract Set getDeferringVariables(); + + /** + * Refine further if needed + */ + @Override + public boolean isReadyAt(SubPlan plan, IQueryMetaContext context) { + return plan.getVisibleVariables().containsAll(getDeferringVariables()); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/AbstractMemorylessAggregationOperator.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/AbstractMemorylessAggregationOperator.java new file mode 100644 index 00000000..63a37bbe --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/AbstractMemorylessAggregationOperator.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.aggregations; + +/** + * + * An aggregation operator that does not store interim results beyond the final aggregated value. + * @author Gabor Bergmann + * @since 1.4 + */ +public abstract class AbstractMemorylessAggregationOperator + implements IMultisetAggregationOperator +{ + + @Override + public AggregateResult getAggregate(AggregateResult result) { + return result; + } + + @Override + public AggregateResult clone(AggregateResult original) { + return original; + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/AggregatorType.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/AggregatorType.java new file mode 100644 index 00000000..4cc40a2b --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/AggregatorType.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.aggregations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import tools.refinery.viatra.runtime.matchers.aggregators.count; + +/** + * The aggregator type annotation describes the type constraints for the selected aggregator. In version 1.4, two kinds of + * aggregators are supported: + * + *

      + *
    1. An aggregator that does not consider any parameter value from the call ({@link count}), just calculates the + * number of matches. This is represented by a single {@link Void} and a single corresponding return type.
    2. + *
    3. An aggregator that considers a single parameter from the call, and executes some aggregate operations over it. + * Such an aggregate operation can be defined over multiple types, where each possible parameter type has a corresponding return type declared.
    4. + *
    + * + * Important! The parameterTypes and returnTypes arrays must have + *
      + *
    • The same number of classes defined each.
    • + *
    • Items are corresponded by index.
    • + *
    • Items should represent data types
    • + *
    + * + * @author Zoltan Ujhelyi + * @since 1.4 + * + */ +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface AggregatorType { + + Class[] parameterTypes(); + + Class[] returnTypes(); +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/BoundAggregator.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/BoundAggregator.java new file mode 100644 index 00000000..e6972544 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/BoundAggregator.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.aggregations; + +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.common.JavaTransitiveInstancesKey; + +/** + * Augments an aggregator operator with type bindings for the type of values being aggregated and the aggregate result. + *

    In case of count, the operator should be null. + * @author Gabor Bergmann + * @since 1.4 + */ +public class BoundAggregator { + private final IMultisetAggregationOperator operator; + private final Class domainType; + private final Class aggregateResultType; + + public BoundAggregator(IMultisetAggregationOperator operator, + Class domainType, + Class aggregateResultType) { + super(); + this.operator = operator; + this.domainType = domainType; + this.aggregateResultType = aggregateResultType; + } + + public IMultisetAggregationOperator getOperator() { + return operator; + } + + public Class getDomainType() { + return domainType; + } + + public Class getAggregateResultType() { + return aggregateResultType; + } + + public IInputKey getDomainTypeAsInputKey() { + return toJavaInputKey(domainType); + } + + public IInputKey getAggregateResultTypeAsInputKey() { + return toJavaInputKey(aggregateResultType); + } + + private static IInputKey toJavaInputKey(Class type) { + if (type==null) { + return null; + } else { + return new JavaTransitiveInstancesKey(type); + } + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/IAggregatorFactory.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/IAggregatorFactory.java new file mode 100644 index 00000000..c970bd6a --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/IAggregatorFactory.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Zoltan Ujhelyi, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.aggregations; + +/** + * + * Describes an aggregation operator keyword, potentially with type polymorphism. The actual runtime + * {@link IMultisetAggregationOperator} that implements the aggregation logic may depend on the type context. + * + *

    + * Implementors are suggested to use lower-case classnames (as it will end up in the language) and are required use the + * annotation {@link AggregatorType} to indicate type inference rules. + * + *

    + * Important! Implemented aggregators must be (1) deterministic (2) pure and (3)support incremental + * value updates in the internal operation. + * + * @author Zoltan Ujhelyi + * @since 1.4 + */ + +public interface IAggregatorFactory { + + /** + * Given type parameters selected from {@link AggregatorType} annotations, returns a run-time aggregator operator + * that is bound to the actual types. + * + * @param domainClass + * Java type of the values that are being aggregated + * @return the actual run-time aggregator logic, with type bindings + */ + public BoundAggregator getAggregatorLogic(Class domainClass); + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/IMultisetAggregationOperator.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/IMultisetAggregationOperator.java new file mode 100644 index 00000000..3bc22274 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/aggregations/IMultisetAggregationOperator.java @@ -0,0 +1,106 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.aggregations; + +import java.util.Collection; +import java.util.stream.Stream; + +import tools.refinery.viatra.runtime.matchers.aggregators.ExtremumOperator; + +/** + * A single column aggregator is used to incrementally compute the aggregate of a multiset of values according to an aggregator operator. + * + *

    The operator provides two methods of computation:

      + *
    • Stateless aggregation of an explicit multiset, provided by {@link #aggregateStatelessly(Collection)}.
    • + *
    • Incremental aggregation, provided by {@link #createNeutral()}, {@link #update(Object, Object, boolean)}, {@link #isNeutral(Object)}, {@link #getAggregate(Object)}. + *
    + * + *

    In case of incremental computation, the aggregable multiset is conceptual; it is not represented by an explicit Collection object, but its update operations are tracked. + * + *

    In case of incremental computation, internal results, potentially distinct from the final aggregate result, may be stored in a helper data structure called accumulator. + * The goal of this distinction is that the final result may not be sufficient for incremental updates (see e.g. {@link ExtremumOperator}). + * + * @author Gabor Bergmann + * + * @param the type of elements to be aggregated. + * @param the type used to store the interim results of the aggregate computation, + * that may be incrementally refreshed upon updates to the multiset, and that can easily yield the final result. + * @param the type of the final result of the aggregation to be output. + * + * @since 1.4 + */ +public interface IMultisetAggregationOperator { + + /** + * A textual description of the operator. + */ + String getShortDescription(); + + /** + * A name or identifier of the operator. + */ + String getName(); + + /** + * @return the neutral element, i.e. the interim result of aggregating an empty multiset. + */ + Accumulator createNeutral(); + + /** + * @return true if the interim result is equivalent to the neutral element, as if there are no values in the multiset. + * Must return true if the multiset is empty. + */ + boolean isNeutral(Accumulator result); + + /** + * @return an updated intermediate result, + * changed to reflect that a given object was added to / removed from the multiset + * (as indicated by the parameter isInsertion) + */ + Accumulator update(Accumulator oldResult, Domain updateValue, boolean isInsertion); + + /** + * @return the aggregate result obtained from the given intermediate result. + * May be null to indicate that the current multiset cannot be aggregated (e.g. 0 elements have no minimum). + */ + AggregateResult getAggregate(Accumulator result); + + /** + * Calculates the aggregate results from a given stream of values; all values are considered as inserted + * @return the aggregate result, or null if no result can be calculated (e.g. because of an empty stream) + * @since 2.0 + */ + AggregateResult aggregateStream(Stream stream); + + /** + * Clones the given accumulator (with all its internal contents). + */ + default Accumulator clone(Accumulator original) { + throw new UnsupportedOperationException(); + } + + /** + * Combines the given aggregate result and accumulator into a single aggregate result. + */ + default AggregateResult combine(AggregateResult left, Accumulator right) { + throw new UnsupportedOperationException(); + } + + default boolean contains(Domain value, Accumulator accumulator) { + throw new UnsupportedOperationException(); + } + + /** + * Pretty prints the contents of the given accumulator. + */ + default String prettyPrint(final Accumulator accumulator) { + return accumulator.toString(); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/analysis/QueryAnalyzer.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/analysis/QueryAnalyzer.java new file mode 100644 index 00000000..e3f28cff --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/analysis/QueryAnalyzer.java @@ -0,0 +1,194 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.analysis; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.stream.Collectors; + +import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.planning.helpers.FunctionalDependencyHelper; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.annotations.PAnnotation; +import tools.refinery.viatra.runtime.matchers.psystem.annotations.ParameterReference; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.ExportedParameter; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.PositivePatternCall; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; + +/** + * Object responsible for computing and caching static query analysis results. + *

    Any client can instantiate this to statically analyze queries. + * Query backends should share an instance obtained via {@link IQueryBackendContext} to save resources. + *

    Precondition: all involved queries must be initialized. + * @noinstantiate Considered unstable API; subject to change in future versions. + * Either use the analyzer provided by {@link IQueryBackendContext}, or anticipate + * potential future breakage when instantiating your own analyzer. + * @author Gabor Bergmann + * @since 1.5 + */ +public final class QueryAnalyzer { + + private IQueryMetaContext metaContext; + + public QueryAnalyzer(IQueryMetaContext metaContext) { + this.metaContext = metaContext; + } + + // Functional dependencies + + /** + * Maps query and strictness to functional dependencies + */ + private Map, Set>> strictFunctionalDependencyGuarantees = + new HashMap<>(); + private Map, Set>> softFunctionalDependencyGuarantees = + new HashMap<>(); + + /** + * Functional dependency information, expressed on query parameters, that the match set of the query is guaranteed to respect. + *

    The type dependencies shall be expressed on the parameter index integers, NOT the {@link PParameter} object. + * @return a non-null map of functional dependencies on parameters that can be processed by {@link FunctionalDependencyHelper} + * @param strict if true, only "hard" dependencies are taken into account that are strictly enforced by the model representation; + * if false, user-provided soft dependencies (@FunctionalDependency) are included as well, that are anticipated but not guaranteed by the storage mechanism; + * use true if superfluous dependencies may taint the correctness of a computation, false if they would merely impact performance + * @since 1.5 + */ + public Map, Set> getProjectedFunctionalDependencies(PQuery query, boolean strict) { + Map, Set>> guaranteeStore = strict ? strictFunctionalDependencyGuarantees : softFunctionalDependencyGuarantees; + Map, Set> dependencies = guaranteeStore.get(query); + // Why not computeIfAbsent? See Bug 532507 + // Invoked method #computeFunctionalDependencies may trigger functional dependency computation for called queries; + // and may thus recurs back into #getProjectedFunctionalDependencies, causing a ConcurrentModificationException + // if the called query has not been previously analyzed. + // + // Note: if patterns are recursive, the empty accumulator will be found in the store + // (this yields a safe lower estimate and guarantees termination for #getProjectedFunctionalDependencies) + // But this case probably will not occur due to recursive queries having a disjunction at some point, + // and thus ignored by #computeFunctionalDependencies + if (dependencies == null) { + dependencies = new HashMap<>(); // accumulator + guaranteeStore.put(query, dependencies); + computeFunctionalDependencies(dependencies, query, strict); + } + return dependencies; + } + + private void computeFunctionalDependencies(Map, Set> accumulator, PQuery query, boolean strict) { + Set bodies = query.getDisjunctBodies().getBodies(); + if (bodies.size() == 1) { // no support for recursion or disjunction + + PBody body = bodies.iterator().next(); + + // collect parameter variables + Map parameters = body.getSymbolicParameters().stream() + .collect(Collectors.toMap(ExportedParameter::getParameterVariable, + param -> query.getParameters().indexOf(param.getPatternParameter()))); + + // collect all internal dependencies + Map, Set> internalDependencies = + getFunctionalDependencies(body.getConstraints(), strict); + + // project onto parameter variables + Map, Set> projectedDeps = + FunctionalDependencyHelper.projectDependencies(internalDependencies, parameters.keySet()); + + // translate into indices + for (Entry, Set> entry : projectedDeps.entrySet()) { + Set left = new HashSet(); + Set right = new HashSet(); + for (PVariable pVariable : entry.getKey()) { + left.add(parameters.get(pVariable)); + } + for (PVariable pVariable : entry.getValue()) { + right.add(parameters.get(pVariable)); + } + accumulator.put(left, right); + } + + } else { + // Disjunctive case, no dependencies are inferred + // TODO: we can still salvage the intersection of dependencies IF + // - all bodies have disjoint match sets + // - and we avoid recursion + } + + // add annotation-based soft dependencies (regardless of number of bodies) + if (!strict) { + outer: + for (PAnnotation annotation : query.getAnnotationsByName("FunctionalDependency")) { + Set lefts = new HashSet(); + Set rights = new HashSet(); + + for (Object object : annotation.getAllValues("forEach")) { + ParameterReference parameter = (ParameterReference) object; + Integer position = query.getPositionOfParameter(parameter.getName()); + if (position == null) continue outer; + lefts.add(position); + } + for (Object object : annotation.getAllValues("unique")) { + ParameterReference parameter = (ParameterReference) object; + Integer position = query.getPositionOfParameter(parameter.getName()); + if (position == null) continue outer; + rights.add(position); + } + + FunctionalDependencyHelper.includeDependency(accumulator, lefts, rights); + } + } + } + + /** + * Functional dependency information, expressed on PVariables within a body, that the selected constraints imply. + * @return a non-null map of functional dependencies on PVariables that can be processed by {@link FunctionalDependencyHelper} + * @param constraints the set of constraints whose consequences will be analyzed + * @param strict if true, only "hard" dependencies are taken into account that are strictly enforced by the model representation; + * if false, user-provided soft dependencies (@FunctionalDependency) are included as well, that are anticipated but not guaranteed by the storage mechanism; + * use true if superfluous dependencies may taint the correctness of a computation, false if they would merely impact performance + * @since 1.5 + */ + public Map, Set> getFunctionalDependencies(Set constraints, boolean strict) { + Map, Set> accumulator = new HashMap, Set>(); + for (PConstraint pConstraint : constraints){ + if (pConstraint instanceof PositivePatternCall) { + // use query analysis results instead + PositivePatternCall call = (PositivePatternCall) pConstraint; + PQuery query = call.getSupplierKey(); + Map, Set> paramDependencies = getProjectedFunctionalDependencies(query, strict); + for (Entry, Set> entry : paramDependencies.entrySet()) { + Set lefts = new HashSet(); + Set rights = new HashSet(); + + for (Integer index : entry.getKey()) { + lefts.add(call.getVariableInTuple(index)); + } + for (Integer index : entry.getValue()) { + rights.add(call.getVariableInTuple(index)); + } + + FunctionalDependencyHelper.includeDependency(accumulator, + lefts, rights); + } + } else { + // delegate to PConstraint + FunctionalDependencyHelper.includeDependencies(accumulator, + pConstraint.getFunctionalDependencies(metaContext)); + } + } + return accumulator; + } + + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/annotations/PAnnotation.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/annotations/PAnnotation.java new file mode 100644 index 00000000..c4fbe0e9 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/annotations/PAnnotation.java @@ -0,0 +1,94 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.annotations; + +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.BiConsumer; + +import org.eclipse.collections.api.multimap.MutableMultimap; +import org.eclipse.collections.impl.multimap.list.FastListMultimap; + +/** + * A container describing query annotations + * @author Zoltan Ujhelyi + * + */ +public class PAnnotation { + + private final String name; + private MutableMultimap attributes = FastListMultimap.newMultimap(); + + public PAnnotation(String name) { + this.name = name; + + } + + /** + * Adds an attribute to the annotation + * @param attributeName + * @param value + */ + public void addAttribute(String attributeName, Object value) { + attributes.put(attributeName, value); + } + + /** + * Return the name of the annotation + */ + public String getName() { + return name; + } + + /** + * Returns the value of the first occurrence of an attribute + * @param attributeName + * @return the attribute value, or null, if attribute is not available + * @since 2.0 + */ + public Optional getFirstValue(String attributeName) { + return getAllValues(attributeName).stream().findFirst(); + } + + /** + * Returns the value of the first occurrence of an attribute + * @param attributeName + * @return the attribute value, or null, if attribute is not available + * @since 2.0 + */ + public Optional getFirstValue(String attributeName, Class clazz) { + return getAllValues(attributeName).stream().filter(clazz::isInstance).map(clazz::cast).findFirst(); + } + + /** + * Returns all values of a selected attribute + * @param attributeName + * @return a non-null, but possibly empty list of attributes + */ + public List getAllValues(String attributeName) { + return attributes.get(attributeName).toList(); + } + + /** + * Executes a consumer over all attributes. A selected attribute name (key) can appear (and thus consumed) multiple times. + * @since 2.0 + */ + public void forEachValue(BiConsumer consumer) { + attributes.forEachKeyValue(consumer::accept); + } + + /** + * Returns a set of all attribute names used in this annotation + * @since 2.1 + */ + public Set getAllAttributeNames() { + return attributes.keySet().toSet(); + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/annotations/ParameterReference.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/annotations/ParameterReference.java new file mode 100644 index 00000000..c67e9046 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/annotations/ParameterReference.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.annotations; + +/** + * An annotation parameter referencing a query parameter by name. Does not check whether the parameter exists. + * + * @author Zoltan Ujhelyi + * + */ +public class ParameterReference { + + final String name; + + public ParameterReference(String name) { + super(); + this.name = name; + } + + public String getName() { + return name; + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/AggregatorConstraint.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/AggregatorConstraint.java new file mode 100644 index 00000000..56f86e89 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/AggregatorConstraint.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.basicdeferred; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.psystem.ITypeInfoProviderConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.TypeJudgement; +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.BoundAggregator; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; + +/** + * The PSystem representation of an aggregation. + * + * @author Tamas Szabo + * @since 1.4 + */ +public class AggregatorConstraint extends PatternCallBasedDeferred implements ITypeInfoProviderConstraint { + + protected PVariable resultVariable; + private BoundAggregator aggregator; + protected int aggregatedColumn; + + public AggregatorConstraint(BoundAggregator aggregator, PBody pBody, Tuple actualParametersTuple, PQuery query, + PVariable resultVariable, int aggregatedColumn) { + super(pBody, actualParametersTuple, query, Collections.singleton(resultVariable)); + this.resultVariable = resultVariable; + this.aggregatedColumn = aggregatedColumn; + this.aggregator = aggregator; + } + + public int getAggregatedColumn() { + return this.aggregatedColumn; + } + + public BoundAggregator getAggregator() { + return this.aggregator; + } + + @Override + public Set getDeducedVariables() { + return Collections.singleton(resultVariable); + } + + @Override + public Map, Set> getFunctionalDependencies(IQueryMetaContext context) { + final Map, Set> result = new HashMap, Set>(); + result.put(getDeferringVariables(), getDeducedVariables()); + return result; + } + + @Override + protected void doDoReplaceVariables(PVariable obsolete, PVariable replacement) { + if (resultVariable.equals(obsolete)) + resultVariable = replacement; + } + + @Override + protected Set getCandidateQuantifiedVariables() { + return actualParametersTuple. getDistinctElements(); + } + + @Override + protected String toStringRest() { + return query.getFullyQualifiedName() + "@" + actualParametersTuple.toString() + "->" + + resultVariable.toString(); + } + + public PVariable getResultVariable() { + return resultVariable; + } + + @Override + public Set getImpliedJudgements(IQueryMetaContext context) { + Set result = new HashSet(); + IInputKey aggregateResultType = aggregator.getAggregateResultTypeAsInputKey(); + if (aggregateResultType != null) { + result.add(new TypeJudgement(aggregateResultType, Tuples.staticArityFlatTupleOf(resultVariable))); + } + return result; + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/BaseTypeSafeConstraint.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/BaseTypeSafeConstraint.java new file mode 100644 index 00000000..7bc949a8 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/BaseTypeSafeConstraint.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.psystem.basicdeferred; + +import java.util.Collections; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.planning.SubPlan; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.TypeJudgement; +import tools.refinery.viatra.runtime.matchers.psystem.VariableDeferredPConstraint; + +/** + * @author Gabor Bergmann + * + */ +public abstract class BaseTypeSafeConstraint extends + VariableDeferredPConstraint { + + protected Set inputVariables; + protected PVariable outputVariable; + + public PVariable getOutputVariable() { + return outputVariable; + } + + /** + * @param pBody + * @param inputVariables + * @param outputVariable null iff no output (check-only) + */ + public BaseTypeSafeConstraint(PBody pBody, + Set inputVariables, final PVariable outputVariable) { + super(pBody, + (outputVariable == null) ? + inputVariables : + Stream.concat(inputVariables.stream(), Stream.of(outputVariable)).collect(Collectors.toSet()) + ); + this.inputVariables = inputVariables; + this.outputVariable = outputVariable; + } + + @Override + public Set getDeducedVariables() { + if (outputVariable == null) + return Collections.emptySet(); + else + return Collections.singleton(outputVariable); + } + + @Override + public Set getDeferringVariables() { + return inputVariables; + } + + @Override + public boolean isReadyAt(SubPlan plan, IQueryMetaContext context) { + if (super.isReadyAt(plan, context)) { + return checkTypeSafety(plan, context) == null; + } + return false; + } + + /** + * Checks whether all type restrictions are already enforced on affected variables. + * + * @param plan + * @return a variable whose type safety is not enforced yet, or null if the plan is typesafe + */ + public PVariable checkTypeSafety(SubPlan plan, IQueryMetaContext context) { + Set impliedJudgements = plan.getAllImpliedTypeJudgements(context); + + for (PVariable pVariable : inputVariables) { + Set allTypeRestrictionsForVariable = pBody.getAllUnaryTypeRestrictions(context).get(pVariable); + if (allTypeRestrictionsForVariable != null && !impliedJudgements.containsAll(allTypeRestrictionsForVariable)) + return pVariable; + } + return null; + } + + @Override + protected void doReplaceVariable(PVariable obsolete, PVariable replacement) { + if (inputVariables.remove(obsolete)) + inputVariables.add(replacement); + if (outputVariable == obsolete) + outputVariable = replacement; + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/Equality.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/Equality.java new file mode 100644 index 00000000..b978b62c --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/Equality.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.psystem.basicdeferred; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.planning.SubPlan; +import tools.refinery.viatra.runtime.matchers.psystem.DeferredPConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; + +/** + * @author Gabor Bergmann + * + */ +public class Equality extends DeferredPConstraint { + + private PVariable who; + private PVariable withWhom; + + public Equality(PBody pBody, PVariable who, PVariable withWhom) { + super(pBody, buildSet(who, withWhom)); + this.who = who; + this.withWhom = withWhom; + } + + private static Set buildSet(PVariable who, PVariable withWhom) { + Set set = new HashSet(); + set.add(who); + set.add(withWhom); + return set; + } + + /** + * An equality is moot if it compares the a variable with itself. + * + * @return true, if the equality is moot + */ + public boolean isMoot() { + return who.equals(withWhom); + } + + @Override + public void doReplaceVariable(PVariable obsolete, PVariable replacement) { + if (obsolete.equals(who)) + who = replacement; + if (obsolete.equals(withWhom)) + withWhom = replacement; + } + + @Override + protected String toStringRest() { + return who.getName() + "=" + withWhom.getName(); + } + + public PVariable getWho() { + return who; + } + + public PVariable getWithWhom() { + return withWhom; + } + + @Override + public Set getDeducedVariables() { + return Collections.emptySet(); + } + + @Override + public Map, Set> getFunctionalDependencies(IQueryMetaContext context) { + final Map, Set> result = new HashMap, Set>(); + result.put(Collections.singleton(who), Collections.singleton(withWhom)); + result.put(Collections.singleton(withWhom), Collections.singleton(who)); + return result; + } + + @Override + public boolean isReadyAt(SubPlan plan, IQueryMetaContext context) { + return plan.getVisibleVariables().contains(who) && plan.getVisibleVariables().contains(withWhom); + // will be replaced by || if copierNode is available; + // until then, LayoutHelper.unifyVariablesAlongEqualities(PSystem) is + // recommended. + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/ExportedParameter.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/ExportedParameter.java new file mode 100644 index 00000000..80706792 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/ExportedParameter.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.psystem.basicdeferred; + +import java.util.Collections; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.planning.QueryProcessingException; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.VariableDeferredPConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; + +/** + * @author Gabor Bergmann + * + */ +public class ExportedParameter extends VariableDeferredPConstraint { + PVariable parameterVariable; + final String parameterName; + final PParameter patternParameter; + + /** + * @since 1.4 + */ + public ExportedParameter(PBody pBody, PVariable parameterVariable, PParameter patternParameter) { + super(pBody, Collections.singleton(parameterVariable)); + this.parameterVariable = parameterVariable; + this.patternParameter = patternParameter; + parameterName = patternParameter.getName(); + } + + @Override + public void doReplaceVariable(PVariable obsolete, PVariable replacement) { + if (obsolete.equals(parameterVariable)) + parameterVariable = replacement; + } + + @Override + protected String toStringRest() { + Object varName = parameterVariable.getName(); + return parameterName.equals(varName) ? parameterName : parameterName + "(" + varName + ")"; + } + + @Override + public Set getDeducedVariables() { + return Collections.emptySet(); + } + + /** + * The name of the parameter; usually, it is expected that {@link #getParameterVariable()} is more useful, except + * maybe for debugging purposes. + * + * @return a non-null name of the parameter + */ + public String getParameterName() { + return parameterName; + } + + public PVariable getParameterVariable() { + return parameterVariable; + } + + /** + * @since 1.4 + */ + public PParameter getPatternParameter() { + if (patternParameter == null) { + PQuery query = pBody.getPattern(); + Integer index = query.getPositionOfParameter(parameterName); + if (index == null) { + throw new IllegalStateException(String.format("Pattern %s does not have a parameter named %s", + query.getFullyQualifiedName(), parameterName)); + } + return query.getParameters().get(index); + } else { + return patternParameter; + } + } + + @Override + public Set getDeferringVariables() { + return Collections.singleton(parameterVariable); + } + + @Override + public void checkSanity() { + super.checkSanity(); + if (!parameterVariable.isDeducable()) { + String[] args = { parameterName }; + String msg = "Impossible to match pattern: " + + "exported pattern variable {1} can not be determined based on the pattern constraints. " + + "HINT: certain constructs (e.g. negative patterns or check expressions) cannot output symbolic parameters."; + String shortMsg = "Could not deduce value of parameter"; + throw new QueryProcessingException(msg, args, shortMsg, null); + } + + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/ExpressionEvaluation.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/ExpressionEvaluation.java new file mode 100644 index 00000000..06688c36 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/ExpressionEvaluation.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.basicdeferred; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.psystem.IExpressionEvaluator; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; + +/** + * @author Zoltan Ujhelyi + * + */ +public class ExpressionEvaluation extends BaseTypeSafeConstraint { + + private IExpressionEvaluator evaluator; + private boolean isUnwinding; + + public ExpressionEvaluation(PBody pBody, IExpressionEvaluator evaluator, PVariable outputVariable) { + this(pBody, evaluator, outputVariable, false); + } + + /** + * @since 2.4 + */ + public ExpressionEvaluation(PBody pBody, IExpressionEvaluator evaluator, PVariable outputVariable, + boolean isUnwinding) { + super(pBody, getPVariablesOfExpression(pBody, evaluator), outputVariable); + this.evaluator = evaluator; + this.isUnwinding = isUnwinding; + } + + /** + * @since 2.4 + */ + public boolean isUnwinding() { + return isUnwinding; + } + + public IExpressionEvaluator getEvaluator() { + return evaluator; + } + + @Override + protected String toStringRest() { + return Tuples.flatTupleOf(new ArrayList(inputVariables).toArray()).toString() + "|=" + + evaluator.getShortDescription(); + } + + @Override + public Map, Set> getFunctionalDependencies(IQueryMetaContext context) { + if (outputVariable == null) + return Collections.emptyMap(); + else + return Collections.singletonMap(inputVariables, Collections.singleton(outputVariable)); + } + + private static Set getPVariablesOfExpression(PBody pBody, IExpressionEvaluator evaluator) { + // use a linked set, so that the variables will come in the order of the parameters + Set result = new LinkedHashSet(); + for (String name : evaluator.getInputParameterNames()) { + PVariable variable = pBody.getOrCreateVariableByName(name); + result.add(variable); + } + return result; + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/Inequality.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/Inequality.java new file mode 100644 index 00000000..5cac33dc --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/Inequality.java @@ -0,0 +1,151 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.psystem.basicdeferred; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.VariableDeferredPConstraint; + +/** + * @author Gabor Bergmann + * + */ +public class Inequality extends VariableDeferredPConstraint { + + private PVariable who; + private PVariable withWhom; + + /** + * The inequality constraint is weak if it can be ignored when who is the same as withWhom, or if any if them is + * undeducible. + */ + private boolean weak; + + public Inequality(PBody pBody, PVariable who, PVariable withWhom) { + this(pBody, who, withWhom, false); + } + + public Inequality(PBody pBody, PVariable who, PVariable withWhom, + boolean weak) { + super(pBody, new HashSet<>(Arrays.asList(who, withWhom) )); + this.who = who; + this.withWhom = withWhom; + this.weak = weak; + } + + // private Inequality( + // PSystem pSystem, + // PVariable subject, Set inequals) + // { + // super(pSystem, include(inequals, subject)); + // this.subject = subject; + // this.inequals = inequals; + // } + + // private static HashSet include(Set inequals, PVariable subject) { + // HashSet hashSet = new HashSet(inequals); + // hashSet.add(subject); + // return hashSet; + // } + + @Override + public Set getDeferringVariables() { + return getAffectedVariables(); + } + + // private static int[] mapIndices(Map variablesIndex, Set keys) { + // int[] result = new int[keys.size()]; + // int k = 0; + // for (PVariable key : keys) { + // result[k++] = variablesIndex.get(key); + // } + // return result; + // } + + // @Override + // public IFoldablePConstraint getIncorporator() { + // return incorporator; + // } + // + // @Override + // public void registerIncorporatationInto(IFoldablePConstraint incorporator) { + // this.incorporator = incorporator; + // } + // + // @Override + // public boolean incorporate(IFoldablePConstraint other) { + // if (other instanceof Inequality) { + // Inequality other2 = (Inequality) other; + // if (subject.equals(other2.subject)) { + // Set newInequals = new HashSet(inequals); + // newInequals.addAll(other2.inequals); + // return new Inequality(buildable, subject, newInequals); + // } + // } else return false; + // } + + @Override + protected String toStringRest() { + return who.toString() + (isWeak() ? "!=?" : "!=") + withWhom.toString(); + } + + @Override + public void doReplaceVariable(PVariable obsolete, PVariable replacement) { + if (obsolete.equals(who)) + who = replacement; + if (obsolete.equals(withWhom)) + withWhom = replacement; + } + + @Override + public Set getDeducedVariables() { + return Collections.emptySet(); + } + + /** + * The inequality constraint is weak if it can be ignored when who is the same as withWhom, or if any if them is + * undeducible. + * + * @return the weak + */ + public boolean isWeak() { + return weak; + } + + /** + * A weak inequality constraint is eliminable if who is the same as withWhom, or if any if them is undeducible. + */ + public boolean isEliminable() { + return isWeak() && (who.equals(withWhom) || !who.isDeducable() || !withWhom.isDeducable()); + } + + /** + * Eliminates a weak inequality constraint if it can be ignored when who is the same as withWhom, or if any if them + * is undeducible. + */ + public void eliminateWeak() { + if (isEliminable()) + delete(); + } + + public PVariable getWho() { + return who; + } + + public PVariable getWithWhom() { + return withWhom; + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/NegativePatternCall.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/NegativePatternCall.java new file mode 100644 index 00000000..87d9d9fc --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/NegativePatternCall.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.psystem.basicdeferred; + +import java.util.Collections; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; + +/** + * @author Gabor Bergmann + * + */ +public class NegativePatternCall extends PatternCallBasedDeferred { + + public NegativePatternCall(PBody pBody, Tuple actualParametersTuple, PQuery query) { + super(pBody, actualParametersTuple, query); + } + + @Override + public Set getDeducedVariables() { + return Collections.emptySet(); + } + + /** + * @return all variables that may potentially be quantified they are not used anywhere else + */ + @Override + protected Set getCandidateQuantifiedVariables() { + return getAffectedVariables(); + } + + @Override + protected void doDoReplaceVariables(PVariable obsolete, PVariable replacement) { + } + + @Override + protected String toStringRest() { + return "!" + query.getFullyQualifiedName() + "@" + actualParametersTuple.toString(); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/PatternCallBasedDeferred.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/PatternCallBasedDeferred.java new file mode 100644 index 00000000..93eeffec --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/PatternCallBasedDeferred.java @@ -0,0 +1,118 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.psystem.basicdeferred; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.planning.QueryProcessingException; +import tools.refinery.viatra.runtime.matchers.psystem.IQueryReference; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.VariableDeferredPConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; + +/** + * @author Gabor Bergmann + * + */ +public abstract class PatternCallBasedDeferred extends VariableDeferredPConstraint implements IQueryReference { + + protected Tuple actualParametersTuple; + + protected abstract void doDoReplaceVariables(PVariable obsolete, PVariable replacement); + + protected abstract Set getCandidateQuantifiedVariables(); + + protected PQuery query; + private Set deferringVariables; + + public PatternCallBasedDeferred(PBody pBody, Tuple actualParametersTuple, + PQuery pattern, Set additionalAffectedVariables) { + super(pBody, union(actualParametersTuple. getDistinctElements(), additionalAffectedVariables)); + this.actualParametersTuple = actualParametersTuple; + this.query = pattern; + } + + public PatternCallBasedDeferred(PBody pBody, Tuple actualParametersTuple, + PQuery pattern) { + this(pBody, actualParametersTuple, pattern, Collections. emptySet()); + } + + private static Set union(Set a, Set b) { + Set result = new HashSet(); + result.addAll(a); + result.addAll(b); + return result; + } + + @Override + public Set getDeferringVariables() { + if (deferringVariables == null) { + deferringVariables = new HashSet(); + for (PVariable var : getCandidateQuantifiedVariables()) { + if (var.isDeducable()) + deferringVariables.add(var); + } + } + return deferringVariables; + } + + @Override + public void checkSanity() { + super.checkSanity(); + for (Object obj : this.actualParametersTuple.getDistinctElements()) { + PVariable var = (PVariable) obj; + if (!getDeferringVariables().contains(var)) { + // so this is a free variable of the NAC / aggregation? + for (PConstraint pConstraint : var.getReferringConstraints()) { + if (pConstraint != this + && !(pConstraint instanceof Equality && ((Equality) pConstraint).isMoot())) + throw new QueryProcessingException ( + "Variable {1} of constraint {2} is not a positively determined part of the pattern, yet it is also affected by {3}.", + new String[] { var.toString(), this.toString(), pConstraint.toString() }, + "Read-only variable can not be deduced", null); + } + } + } + + } + +// public SubPlan getSidePlan(IOperationCompiler compiler) throws QueryPlannerException { +// SubPlan sidePlan = compiler.patternCallPlan(actualParametersTuple, query); +// sidePlan = BuildHelper.enforceVariableCoincidences(compiler, sidePlan); +// return sidePlan; +// } + + @Override + protected void doReplaceVariable(PVariable obsolete, PVariable replacement) { + if (deferringVariables != null) { + // FAIL instead of hopeless attempt to fix + // if (deferringVariables.remove(obsolete)) deferringVariables.add(replacement); + throw new IllegalStateException("Cannot replace variables on " + this + + " when deferring variables have already been identified."); + } + actualParametersTuple = actualParametersTuple.replaceAll(obsolete, replacement); + doDoReplaceVariables(obsolete, replacement); + } + + public Tuple getActualParametersTuple() { + return actualParametersTuple; + } + + @Override + public PQuery getReferredQuery() { + return query; + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/PatternMatchCounter.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/PatternMatchCounter.java new file mode 100644 index 00000000..0c40d91e --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/PatternMatchCounter.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.psystem.basicdeferred; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; + +/** + * @author Gabor Bergmann + */ +public class PatternMatchCounter extends PatternCallBasedDeferred { + + private PVariable resultVariable; + + public PatternMatchCounter(PBody pBody, Tuple actualParametersTuple, + PQuery query, PVariable resultVariable) { + super(pBody, actualParametersTuple, query, Collections.singleton(resultVariable)); + this.resultVariable = resultVariable; + } + + @Override + public Set getDeducedVariables() { + return Collections.singleton(resultVariable); + } + + @Override + public Map, Set> getFunctionalDependencies(IQueryMetaContext context) { + final Map, Set> result = new HashMap, Set>(); + result.put(getDeferringVariables(), getDeducedVariables()); + return result; + } + + @Override + protected void doDoReplaceVariables(PVariable obsolete, PVariable replacement) { + if (resultVariable.equals(obsolete)) + resultVariable = replacement; + } + + @Override + protected Set getCandidateQuantifiedVariables() { + return actualParametersTuple. getDistinctElements(); + } + + + @Override + protected String toStringRest() { + return query.getFullyQualifiedName() + "@" + actualParametersTuple.toString() + "->" + + resultVariable.toString(); + } + + public PVariable getResultVariable() { + return resultVariable; + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/RelationEvaluation.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/RelationEvaluation.java new file mode 100644 index 00000000..336a83fb --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/RelationEvaluation.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2010-2022, Tamas Szabo, GitHub + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.basicdeferred; + +import java.util.List; + +import tools.refinery.viatra.runtime.matchers.psystem.EnumerablePConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.IMultiQueryReference; +import tools.refinery.viatra.runtime.matchers.psystem.IRelationEvaluator; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; + +/** + * A constraint which prescribes the evaluation of custom Java logic that takes an arbitrary number of input relations + * and produces one output relation. Contrast this to {@link ExpressionEvaluation}, which produces a single output value + * given an input tuple. + * + * The assumption is that the relation evaluation logic is not incremental, that is, it can only perform from-scratch + * computation of the output relation given the complete input relations. To this end, the relation evaluator always + * receives the complete input relations with all their contents as input. However, the evaluator engine makes sure that + * the output of the relation evaluation is at least "seemingly" incremental. This means that the underlying computation + * network computes the delta on the output compared to the previous output and only propagates the delta further. + * + * @author Tamas Szabo + * + * @since 2.8 + * + */ +public class RelationEvaluation extends EnumerablePConstraint implements IMultiQueryReference { + + private final IRelationEvaluator evaluator; + private final List inputQueries; + + public RelationEvaluation(final PBody body, final Tuple variablesTuple, final List inputQueries, + final IRelationEvaluator evaluator) { + super(body, variablesTuple); + this.evaluator = evaluator; + this.inputQueries = inputQueries; + } + + public IRelationEvaluator getEvaluator() { + return this.evaluator; + } + + @Override + public List getReferredQueries() { + return this.inputQueries; + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/TypeFilterConstraint.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/TypeFilterConstraint.java new file mode 100644 index 00000000..8b6e29ef --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicdeferred/TypeFilterConstraint.java @@ -0,0 +1,105 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.basicdeferred; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.psystem.ITypeConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.TypeJudgement; +import tools.refinery.viatra.runtime.matchers.psystem.VariableDeferredPConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.TypeConstraint; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; + +/** + * Represents a non-enumerable type constraint that asserts that values substituted for the given tuple of variables + * form a tuple that belongs to a (typically non-enumerable) extensional relation identified by an {@link IInputKey}. + * + *

    The InputKey is typically not enumerable. If it is enumerable, use {@link TypeConstraint} instead, so that the PConstraint carries over the property of enumerability. + * + * @author Bergmann Gabor + * + */ +public class TypeFilterConstraint extends VariableDeferredPConstraint implements + ITypeConstraint { + + private Tuple variablesTuple; + private IInputKey inputKey; + + private TypeJudgement equivalentJudgement; + + + public TypeFilterConstraint(PBody pBody, Tuple variablesTuple, IInputKey inputKey) { + super(pBody, variablesTuple. getDistinctElements()); + this.equivalentJudgement = new TypeJudgement(inputKey, variablesTuple); + + this.variablesTuple = variablesTuple; + this.inputKey = inputKey; + + if (variablesTuple.getSize() != inputKey.getArity()) + throw new IllegalArgumentException( + this.getClass().getSimpleName() + + " applied for variable tuple " + variablesTuple + " having wrong arity for input key " + + inputKey); + } + + + + public Tuple getVariablesTuple() { + return variablesTuple; + } + + public IInputKey getInputKey() { + return inputKey; + } + + @Override + public TypeJudgement getEquivalentJudgement() { + return equivalentJudgement; + } + + @Override + protected void doReplaceVariable(PVariable obsolete, PVariable replacement) { + variablesTuple = variablesTuple.replaceAll(obsolete, replacement); + this.equivalentJudgement = new TypeJudgement(inputKey, variablesTuple); + } + + @Override + public Set getImpliedJudgements(IQueryMetaContext context) { + return Collections.singleton(equivalentJudgement); + } + + @Override + public Set getDeducedVariables() { + return Collections.emptySet(); + } + + @Override + public Set getDeferringVariables() { + return getAffectedVariables(); + } + + @Override + protected String toStringRest() { + return inputKey.getPrettyPrintableName() + "@" + variablesTuple; + } + + @Override + public Map, Set> getFunctionalDependencies(IQueryMetaContext context) { + return TypeConstraintUtil.getFunctionalDependencies(context, inputKey, variablesTuple); + } + + + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/AbstractTransitiveClosure.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/AbstractTransitiveClosure.java new file mode 100644 index 00000000..7bbf7118 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/AbstractTransitiveClosure.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.basicenumerables; + +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.psystem.IQueryReference; +import tools.refinery.viatra.runtime.matchers.psystem.ITypeInfoProviderConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.KeyedEnumerablePConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.TypeJudgement; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; + +/** + * @since 2.0 + */ +public abstract class AbstractTransitiveClosure extends KeyedEnumerablePConstraint implements IQueryReference, ITypeInfoProviderConstraint { + + public AbstractTransitiveClosure(PBody pBody, Tuple variablesTuple, PQuery supplierKey) { + super(pBody, variablesTuple, supplierKey); + } + + @Override + public PQuery getReferredQuery() { + return supplierKey; + } + + /** + * @since 1.3 + */ + @Override + public Set getImpliedJudgements(IQueryMetaContext context) { + return PositivePatternCall.getTypesImpliedByCall(supplierKey, variablesTuple); + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/BinaryReflexiveTransitiveClosure.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/BinaryReflexiveTransitiveClosure.java new file mode 100644 index 00000000..e3dae240 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/BinaryReflexiveTransitiveClosure.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Zoltan Ujhelyi, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.psystem.basicenumerables; + +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.planning.QueryProcessingException; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; + +/** + * For a binary base pattern over an enumerable universe type, computes the reflexive transitive closure (base)* + * + * @author Gabor Bergmann, Zoltan Ujhelyi + * @since 2.0 + */ +public class BinaryReflexiveTransitiveClosure extends AbstractTransitiveClosure { + + private final IInputKey universeType; + + public BinaryReflexiveTransitiveClosure(PBody pBody, Tuple variablesTuple, + PQuery pattern, IInputKey universeType) { + super(pBody, variablesTuple, pattern); + this.universeType = universeType; + } + + @Override + protected String keyToString() { + return supplierKey.getFullyQualifiedName() + "*"; + } + + /** + * Returns the type whose instances should be returned as 0-long paths. + * @since 2.0 + */ + public IInputKey getUniverseType() { + return universeType; + } + + @Override + public void checkSanity() { + if (!universeType.isEnumerable() || universeType.getArity() != 1) { + throw new QueryProcessingException( + String.format("Invalid universe type %s - it should be enumerable and must have an arity of 1.", + universeType.getPrettyPrintableName()), + pBody.getPattern()); + } + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/BinaryTransitiveClosure.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/BinaryTransitiveClosure.java new file mode 100644 index 00000000..716d043b --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/BinaryTransitiveClosure.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.psystem.basicenumerables; + +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; + +/** + * For a binary base pattern, computes the irreflexive transitive closure (base)+ + * + * @author Gabor Bergmann + * + */ +public class BinaryTransitiveClosure extends AbstractTransitiveClosure { + + public BinaryTransitiveClosure(PBody pBody, Tuple variablesTuple, + PQuery pattern) { + super(pBody, variablesTuple, pattern); + } + + @Override + protected String keyToString() { + return supplierKey.getFullyQualifiedName() + "+"; + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/Connectivity.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/Connectivity.java new file mode 100644 index 00000000..10da2e21 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/Connectivity.java @@ -0,0 +1,11 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.viatra.runtime.matchers.psystem.basicenumerables; + +public enum Connectivity { + WEAK, + STRONG; +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/ConstantValue.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/ConstantValue.java new file mode 100644 index 00000000..251146c8 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/ConstantValue.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.psystem.basicenumerables; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.psystem.KeyedEnumerablePConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; + +/** + * @author Gabor Bergmann + * + */ +public class ConstantValue extends KeyedEnumerablePConstraint { + + private PVariable variable; + + public ConstantValue(PBody pBody, PVariable variable, Object value) { + super(pBody, Tuples.staticArityFlatTupleOf(variable), value); + this.variable = variable; + } + + @Override + protected String keyToString() { + return supplierKey.toString(); + } + + /** + * @since 1.7 + */ + public PVariable getVariable() { + return variable; + } + + @Override + public Map, Set> getFunctionalDependencies(IQueryMetaContext context) { + final Map, Set> result = new HashMap, Set>(); + final Set emptySet = Collections.emptySet(); // a constant value is functionally determined by everything + result.put(emptySet, Collections.singleton(getVariableInTuple(0))); + return result; + } + + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/PositivePatternCall.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/PositivePatternCall.java new file mode 100644 index 00000000..25ab34b4 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/PositivePatternCall.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.psystem.basicenumerables; + +import java.util.HashSet; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.psystem.IQueryReference; +import tools.refinery.viatra.runtime.matchers.psystem.ITypeInfoProviderConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.KeyedEnumerablePConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.TypeJudgement; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; + +/** + * @author Gabor Bergmann + * + */ +public class PositivePatternCall extends KeyedEnumerablePConstraint implements IQueryReference, ITypeInfoProviderConstraint { + + public PositivePatternCall(PBody pBody, Tuple variablesTuple, + PQuery pattern) { + super(pBody, variablesTuple, pattern); + } + + @Override + protected String keyToString() { + return supplierKey.getFullyQualifiedName(); + } + + // Note: #getFunctionalDependencies is intentionally not implemented - use QueryAnalyzer instead! +// @Override +// public Map, Set> getFunctionalDependencies(IQueryMetaContext context) { +// return super.getFunctionalDependencies(context); +// } + + @Override + public PQuery getReferredQuery() { + return supplierKey; + } + + @Override + public Set getImpliedJudgements(IQueryMetaContext context) { + return getTypesImpliedByCall(supplierKey, variablesTuple); + } + + /** + * @since 1.3 + */ + public static Set getTypesImpliedByCall(PQuery calledQuery, Tuple actualParametersTuple) { + Set result = new HashSet(); + for (TypeJudgement parameterJudgement : calledQuery.getTypeGuarantees()) { + IInputKey inputKey = parameterJudgement.getInputKey(); + Tuple judgementIndexTuple = parameterJudgement.getVariablesTuple(); + + Object[] judgementVariables = new Object[judgementIndexTuple.getSize()]; + for (int i=0; i + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.viatra.runtime.matchers.psystem.basicenumerables; + +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.psystem.*; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; + +import java.util.Set; + +public class RepresentativeElectionConstraint extends KeyedEnumerablePConstraint + implements IQueryReference, ITypeInfoProviderConstraint { + private final Connectivity connectivity; + + public RepresentativeElectionConstraint(PBody pBody, Tuple variablesTuple, PQuery supplierKey, + Connectivity connectivity) { + super(pBody, variablesTuple, supplierKey); + this.connectivity = connectivity; + } + + public Connectivity getConnectivity() { + return connectivity; + } + + @Override + public PQuery getReferredQuery() { + return supplierKey; + } + + @Override + public Set getImpliedJudgements(IQueryMetaContext context) { + return PositivePatternCall.getTypesImpliedByCall(supplierKey, variablesTuple); + } + + @Override + protected String keyToString() { + return supplierKey.getFullyQualifiedName() + "#" + connectivity + "#representative"; + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/TypeConstraint.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/TypeConstraint.java new file mode 100644 index 00000000..2ca54cc0 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/basicenumerables/TypeConstraint.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.basicenumerables; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.psystem.ITypeConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.KeyedEnumerablePConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.TypeJudgement; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; + +/** + * Represents an enumerable type constraint that asserts that values substituted for the given tuple of variables + * form a tuple that belongs to an enumerable extensional relation identified by an {@link IInputKey}. + * + *

    The InputKey must be enumerable! + * + * @author Zoltan Ujhelyi + * + */ +public class TypeConstraint extends KeyedEnumerablePConstraint implements ITypeConstraint { + + private TypeJudgement equivalentJudgement; + + public TypeConstraint(PBody pBody, Tuple variablesTuple, IInputKey inputKey) { + super(pBody, variablesTuple, inputKey); + this.equivalentJudgement = new TypeJudgement(inputKey, variablesTuple); + + if (! inputKey.isEnumerable()) + throw new IllegalArgumentException( + this.getClass().getSimpleName() + + " applicable for enumerable input keys only; received instead " + + inputKey); + if (variablesTuple.getSize() != inputKey.getArity()) + throw new IllegalArgumentException( + this.getClass().getSimpleName() + + " applied for variable tuple " + variablesTuple + " having wrong arity for input key " + + inputKey); + } + + @Override + protected String keyToString() { + return supplierKey.getPrettyPrintableName(); + } + + @Override + public TypeJudgement getEquivalentJudgement() { + return equivalentJudgement; + } + + @Override + public Set getImpliedJudgements(IQueryMetaContext context) { + return Collections.singleton(equivalentJudgement); + //return equivalentJudgement.getDirectlyImpliedJudgements(context); + } + + @Override + public Map, Set> getFunctionalDependencies(IQueryMetaContext context) { + return TypeConstraintUtil.getFunctionalDependencies(context, supplierKey, variablesTuple); + } + + @Override + public void doReplaceVariable(PVariable obsolete, PVariable replacement) { + super.doReplaceVariable(obsolete, replacement); + this.equivalentJudgement = new TypeJudgement(getSupplierKey(), variablesTuple); + } +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/BasePQuery.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/BasePQuery.java new file mode 100644 index 00000000..2c03a894 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/BasePQuery.java @@ -0,0 +1,231 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.queries; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactory; +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.TypeJudgement; +import tools.refinery.viatra.runtime.matchers.psystem.annotations.PAnnotation; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; + +/** + * Default implementation of PQuery. + * + * @author Bergmann Gabor + */ +public abstract class BasePQuery implements PQuery { + + protected PQueryStatus status = PQueryStatus.UNINITIALIZED; + /** + * @since 2.0 + */ + protected final PVisibility visibility; + protected List pProblems = new ArrayList(); + private List annotations = new ArrayList(); + private QueryEvaluationHint evaluationHints = new QueryEvaluationHint(null, (IQueryBackendFactory)null); + PDisjunction canonicalDisjunction; + private List parameterNames = null; // Lazy initialization + + /** For traceability only. */ + private List wrappingQuerySpecifications = new ArrayList(1); + + @Override + public Integer getPositionOfParameter(String parameterName) { + ensureInitialized(); + int index = getParameterNames().indexOf(parameterName); + return index != -1 ? index : null; + } + + protected void setStatus(PQueryStatus newStatus) { + this.status = newStatus; + } + + protected void addError(PProblem problem) { + status = PQueryStatus.ERROR; + pProblems.add(problem); + } + + @Override + public PQueryStatus getStatus() { + return status; + } + + @Override + public List getPProblems() { + return Collections.unmodifiableList(pProblems); + } + + @Override + public boolean isMutable() { + return status.equals(PQueryStatus.UNINITIALIZED) || status.equals(PQueryStatus.INITIALIZING); + } + + @Override + public void checkMutability() { + Preconditions.checkState(isMutable(), "Cannot edit query definition %s", getFullyQualifiedName()); + } + + /** + * @since 1.5 + */ + public void setEvaluationHints(QueryEvaluationHint hints) { + checkMutability(); + this.evaluationHints = hints; + } + + @Override + public QueryEvaluationHint getEvaluationHints() { + ensureInitialized(); + return evaluationHints; + // TODO instead of field, compute something from annotations? + } + + protected void addAnnotation(PAnnotation annotation) { + checkMutability(); + annotations.add(annotation); + } + + @Override + public List getAllAnnotations() { + ensureInitialized(); + return new ArrayList<>(annotations); + } + + private Stream getAnnotationStreamByName(final String name) { + ensureInitialized(); + return annotations.stream().filter(Objects::nonNull).filter(annotation -> Objects.equals(name, annotation.getName())); + } + + @Override + public List getAnnotationsByName(final String annotationName) { + return getAnnotationStreamByName(annotationName).collect(Collectors.toList()); + } + + @Override + public Optional getFirstAnnotationByName(String annotationName) { + return getAnnotationStreamByName(annotationName).findFirst(); + } + + @Override + public List getParameterNames() { + ensureInitialized(); + if (parameterNames == null) { + parameterNames = getParameters().stream().map(PParameter::getName).collect(Collectors.toList()); + } + return parameterNames; + } + + @Override + public Set getDirectReferredQueries() { + ensureInitialized(); + return canonicalDisjunction.getDirectReferredQueries(); + } + + @Override + public Set getAllReferredQueries() { + ensureInitialized(); + return canonicalDisjunction.getAllReferredQueries(); + } + + + @Override + public List publishedAs() { + return wrappingQuerySpecifications; + } + + @Override + public Set getTypeGuarantees() { + ensureInitialized(); + Set result = new HashSet(); + + List parameters = getParameters(); + for (int i=0; i bodies) { + canonicalDisjunction = new PDisjunction(this, bodies); + for (PBody body : canonicalDisjunction.getBodies()) { + body.setStatus(null); + } + setStatus(PQueryStatus.OK); + } + + /** + * Creates and returns the bodies of the query. If recalled again, a new instance is created. + * + * @return + * @throws ViatraQueryRuntimeException + */ + protected abstract Set doGetContainedBodies(); + + @Override + public String toString() { + return String.format("PQuery<%s>=%s", getFullyQualifiedName(), super.toString()); + } + + /** + * @since 2.0 + */ + @Override + public PVisibility getVisibility() { + return visibility; + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PDisjunction.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PDisjunction.java new file mode 100644 index 00000000..eae4eacf --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PDisjunction.java @@ -0,0 +1,104 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.queries; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.stream.Collectors; + +import tools.refinery.viatra.runtime.matchers.psystem.PBody; + +/** + * + * A disjunction is a set of bodies representing separate conditions. A {@link PQuery} has a single, canonical + * PDisjunction, that can be replaced using rewriter + * + * @author Zoltan Ujhelyi + * + */ +public class PDisjunction { + + private Set bodies; + private PQuery query; + + public PDisjunction(Set bodies) { + this(bodies.iterator().next().getPattern(), bodies); + } + + public PDisjunction(PQuery query, Set bodies) { + super(); + this.query = query; + this.bodies = Collections.unmodifiableSet(new LinkedHashSet<>(bodies)); + this.bodies.forEach(body -> body.setContainerDisjunction(this)); + } + + /** + * Returns an immutable set of bodies that consists of this disjunction + * + * @return the bodies + */ + public Set getBodies() { + return bodies; + } + + /** + * Returns the corresponding query specification. May be null if not set. + */ + public PQuery getQuery() { + return query; + } + + /** + * Returns all queries directly referred in the constraints. They are all required to evaluate this query + * + * @return a non-null, but possibly empty list of query definitions + */ + public Set getDirectReferredQueries() { + return this.getBodies().stream(). + flatMap(PQueries.directlyReferencedQueriesFunction()). // flatten stream of streams + collect(Collectors.toCollection(LinkedHashSet::new)); + } + + /** + * Returns all queries required to evaluate this query (transitively). + * + * @return a non-null, but possibly empty list of query definitions + */ + public Set getAllReferredQueries() { + Set processedQueries = new LinkedHashSet<>(); + processedQueries.add(this.getQuery()); + Set foundQueries = getDirectReferredQueries(); + Set newQueries = new LinkedHashSet<>(foundQueries); + + while(!processedQueries.containsAll(newQueries)) { + PQuery query = newQueries.iterator().next(); + processedQueries.add(query); + newQueries.remove(query); + Set referred = query.getDirectReferredQueries(); + referred.removeAll(processedQueries); + foundQueries.addAll(referred); + newQueries.addAll(referred); + } + return foundQueries; + } + + /** + * Decides whether a disjunction is mutable. A disjunction is mutable if all its contained bodies are mutable. + * + */ + public boolean isMutable() { + for (PBody body : bodies) { + if (!body.isMutable()) { + return false; + } + } + return true; + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PParameter.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PParameter.java new file mode 100644 index 00000000..07165aa2 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PParameter.java @@ -0,0 +1,105 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.queries; + +import java.util.Objects; + +import tools.refinery.viatra.runtime.matchers.context.IInputKey; + +/** + * A descriptor for declared PQuery parameters. A parameter has a name, a declared type and a direction constraint + * + * @author Zoltan Ujhelyi + * + */ +public class PParameter { + + private final String name; + private final String typeName; + private final IInputKey declaredUnaryType; + private final PParameterDirection direction; + + public PParameter(String name) { + this(name, (String) null); + } + + public PParameter(String name, String typeName) { + this(name, typeName, (IInputKey) null); + } + + public PParameter(String name, String typeName, IInputKey declaredUnaryType) { + this(name, typeName, declaredUnaryType, PParameterDirection.INOUT); + } + + /** + * @since 1.4 + */ + public PParameter(String name, String typeName, IInputKey declaredUnaryType, PParameterDirection direction) { + super(); + this.name = name; + this.typeName = typeName; + this.declaredUnaryType = declaredUnaryType; + this.direction = direction; + + if (declaredUnaryType != null && declaredUnaryType.getArity() != 1) { + throw new IllegalArgumentException( + "PParameter declared type must be unary instead of " + declaredUnaryType.getPrettyPrintableName()); + } + } + + /** + * @return the direction + * @since 1.4 + */ + public PParameterDirection getDirection() { + return direction; + } + + /** + * @return the name of the parameter + */ + public String getName() { + return name; + } + + /** + * Returns a textual representation of the declared type of the parameter + * + * @return the type description, or null if not available + */ + public String getTypeName() { + return typeName; + } + + /** + * Yield an {@link IInputKey} representation of the type declared for this parameter. + * + * @return the unary type that was declared on this parameter in the query header, or null if not available + */ + public IInputKey getDeclaredUnaryType() { + return declaredUnaryType; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof PParameter) { + return Objects.equals(name, ((PParameter) obj).name) + && Objects.equals(typeName, ((PParameter) obj).typeName) + && Objects.equals(declaredUnaryType, ((PParameter) obj).declaredUnaryType) + && Objects.equals(direction, ((PParameter) obj).direction); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(name, typeName, declaredUnaryType); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PParameterDirection.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PParameterDirection.java new file mode 100644 index 00000000..c94d4797 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PParameterDirection.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Grill Balázs, IncQueryLabs + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.queries; + +/** + * Values of this enum describe a constraint to the calling of patterns regarding its parameters. + * + * @author Grill Balázs + * @since 1.4 + * + */ +public enum PParameterDirection { + + /** + * Default value, no additional constraint is applied + */ + INOUT, + + /** + * The parameters marked with this constraints shall be set to a value before calling the pattern + */ + IN, + + /** + * The parameters marked with this constraints shall not be set to a value before calling the pattern + */ + OUT + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PProblem.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PProblem.java new file mode 100644 index 00000000..1fe4f541 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PProblem.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.queries; + +import tools.refinery.viatra.runtime.matchers.planning.QueryProcessingException; + +/** + * Represents an error that was detected while the {@link PQuery} object was built from a source. + * @author Bergmann Gabor + * + */ +public class PProblem { + + private final String shortMessage; + private final String location; + private final Exception exception; + + public PProblem(String shortMessage) { + this(null, shortMessage, null, null); + } + /** + * @since 2.0 + */ + public PProblem(String shortMessage, Integer line, Integer column) { + this(null, shortMessage, line, column); + } + public PProblem(QueryProcessingException exception) { + this(exception, exception.getShortMessage(), null, null); + } + public PProblem(Exception exception, String shortMessage) { + this(exception, shortMessage, null, null); + } + + /** + * @since 2.0 + */ + public PProblem(Exception exception, String shortMessage, Integer line, Integer column) { + this.shortMessage = shortMessage; + this.exception = exception; + if (line == null) { + location = "Unspecified location"; + } else if (column == null) { + location = String.format("Line %d", line); + } else { + location = String.format("Line %d Column %d", line, column); + } + } + + public String getShortMessage() { + return shortMessage; + } + public Exception getException() { + return exception; + } + /** + * @since 2.0 + */ + public String getLocation() { + return location; + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PQueries.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PQueries.java new file mode 100644 index 00000000..56f8ca76 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PQueries.java @@ -0,0 +1,110 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.queries; + +import java.util.HashSet; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.psystem.IMultiQueryReference; +import tools.refinery.viatra.runtime.matchers.psystem.ITypeConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.PTraceable; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.TypeConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery.PQueryStatus; + +/** + * Utility class for using PQueries in functional/streaming collection operations effectively + * + * @author Zoltan Ujhelyi + * + */ +public final class PQueries { + + /** + * Hidden constructor for utility class + */ + private PQueries() { + } + + /** + * Predicate checking for the status of selected queries + * + */ + public static Predicate queryStatusPredicate(final PQueryStatus status) { + return query -> query.getStatus().equals(status); + } + + /** + * Enumerates referred queries (without duplicates) for the given body + */ + public static Function> directlyReferencedQueriesFunction() { + return body -> (body.getConstraintsOfType(IMultiQueryReference.class).stream() + .flatMap(e -> e.getReferredQueries().stream()).distinct()); + } + + /** + * Enumerates directly referred extensional relations (without duplicates) in the canonical form of the given query + * + * @param enumerablesOnly + * only enumerable type constraints are considered + * @since 2.0 + */ + public static Stream directlyRequiredTypesOfQuery(PQuery query, boolean enumerablesOnly) { + return directlyRequiredTypesOfDisjunction(query.getDisjunctBodies(), enumerablesOnly); + } + + /** + * Enumerates directly referred extensional relations (without duplicates) for the given formulation of a query. + * + * @param enumerablesOnly + * only enumerable type constraints are considered + * @since 2.0 + */ + public static Stream directlyRequiredTypesOfDisjunction(PDisjunction disjunctBodies, + boolean enumerablesOnly) { + Class filterClass = enumerablesOnly ? TypeConstraint.class : ITypeConstraint.class; + return disjunctBodies.getBodies().stream().flatMap(body -> body.getConstraintsOfType(filterClass).stream()) + .map(constraint -> constraint.getEquivalentJudgement().getInputKey()).distinct(); + } + + /** + * @since 1.4 + */ + public static Predicate parameterDirectionPredicate(final PParameterDirection direction) { + return input -> input.getDirection() == direction; + } + + /** + * Returns all {@link PTraceable}s contained in the given {@link PQuery}: itself, its bodies and their constraints. + * + * @since 1.6 + */ + public static Set getTraceables(PQuery query) { + final Set traceables = new HashSet<>(); + traceables.add(query); + query.getDisjunctBodies().getBodies().forEach(body -> { + traceables.add(body); + body.getConstraints().forEach(traceables::add); + }); + return traceables; + } + + /** + * Calculates the simple name related from a given qualified name by finding the part after the last '.' character. + * + * @since 2.0 + */ + public static String calculateSimpleName(String qualifiedName) { + return qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1); + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PQuery.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PQuery.java new file mode 100644 index 00000000..a909c650 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PQuery.java @@ -0,0 +1,154 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.queries; + +import java.util.List; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackend; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendHintProvider; +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.PTraceable; +import tools.refinery.viatra.runtime.matchers.psystem.TypeJudgement; + +/** + * Internal representation of a query / graph pattern (using a constraint system formalism), + * to be interpreted by a query evaluator ({@link IQueryBackend}). + * End-users of VIATRA Query should access a query as an IQuerySpecification instead. + * + *

    + * PQuerys are definitions of queries usable inside pattern descriptions. Such description always has (a non-null) name. The query + * itself is defined as a (non-empty) set of {@link PBody} instances, the result is the disjunction of the single + * {@link PBody} instances.

    + *

    + * A PQuery might be constructed from erroneous patterns or might be uninitialized - this is represented by its status. + * + * @author Zoltan Ujhelyi + * @since 0.8.0 + * @noimplement This interface is not intended to be implemented by clients. Use {@link BasePQuery} as a base class instead. + */ +public interface PQuery extends PQueryHeader, PTraceable { + + // TODO rewritten as / rewritten from traceability to PDisjunction? + + /** + * @author Zoltan Ujhelyi + * + */ + public enum PQueryStatus { + /** + * Marks that the query definition is not initialized + */ + UNINITIALIZED, + /** + * Marks that the query definition is being initialized + * @since 1.4 + */ + INITIALIZING, + /** + * The query definition was successfully initialized + */ + OK, + /** + * The query definition was initialized, but some issues were present + */ + WARNING, + /** + * The query definition was not successfully initialized because of an error + */ + ERROR + } + + /** + * Returns all bodies associated with the query in their canonical form. If called multiple times, the same set with + * the same contents will be returned. + * + */ + PDisjunction getDisjunctBodies(); + + /** + * Returns all queries directly referred in the constraints. They are all required to evaluate this query + * + * @return a non-null, but possibly empty list of query definitions + */ + Set getDirectReferredQueries(); + + /** + * Returns all queries required to evaluate this query (transitively). + * + * @return a non-null, but possibly empty list of query definitions + */ + Set getAllReferredQueries(); + + /** + * Returns the initialization status of the definition + * + */ + PQueryStatus getStatus(); + + /** + * Returns a list describing the problems that were found in this query. + * + *

    TODO: formulate invariant connecting {@link #getPProblems()} and {@link #getStatus()}. + * + * @return a non-null, but possibly empty list of problems + */ + List getPProblems(); + + /** + * Before a modification operation is executed, a mutability check is performed (via the {@link #getStatus()} + * implementation, and in case of problems an {@link IllegalStateException} is thrown. + */ + void checkMutability(); + + /** + * An option to check mutability of the query. It can be used to avoid getting an {@link IllegalStateException} by + * the execution of {@link #checkMutability()}. + * + * @return true if the query specification is still editable + */ + boolean isMutable(); + + /** + * Optional hints regarding the query evaluation strategy, to be interpreted by the query engine. + *

    To ensure the possibility of external overrides, + * the evaluation engine should not directly consult this field, + * but use an {@link IQueryBackendHintProvider} instead. + */ + public QueryEvaluationHint getEvaluationHints(); + + + /** + * Type information, expressed on query parameters, that all matches of the query are guaranteed to respect. + *

    At the very minimum, this should include the declared types of the parameters. + *

    The type judgement tuples shall contain the parameter index, NOT the {@link PParameter} object. + * + * @return a non-null set of type judgements that the query guarantees for its matches + */ + public Set getTypeGuarantees(); + + /** + * If the query definition is uninitialized, initializes it. + * @throws ViatraQueryRuntimeException if initialization of query specification fails + */ + public abstract void ensureInitialized(); + + /** + * Returns the end-user query specification API objects that wrap this query. + * + *

    Intended for traceability and debug purposes, not part of normal operation. + * Returned list is intended to be appended during query specification construction time. + * + * @return a non-null, but possibly empty list of query specification objects; + */ + List publishedAs(); + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PQueryHeader.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PQueryHeader.java new file mode 100644 index 00000000..f3671934 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PQueryHeader.java @@ -0,0 +1,101 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.queries; + +import java.util.List; +import java.util.Optional; + +import tools.refinery.viatra.runtime.matchers.psystem.annotations.PAnnotation; + +/** + * Represents header information (metainfo) about a query. + *

    To be implemented both by IQuerySpecifications intended for end users, + * and the internal query representation {@link PQuery}. + * + * + * @author Bergmann Gabor + * @since 0.9 + */ +public interface PQueryHeader { + + /** + * Identifies the pattern for which matchers can be instantiated. + */ + public String getFullyQualifiedName(); + + /** + * Return the list of parameter names + * + * @return a non-null, but possibly empty list of parameter names + */ + public List getParameterNames(); + + /** + * Returns a list of parameter descriptions + * + * @return a non-null, but possibly empty list of parameter descriptions + */ + public List getParameters(); + + /** + * Returns the index of a named parameter + * + * @param parameterName + * @return the index, or null of no such parameter is available + */ + public Integer getPositionOfParameter(String parameterName); + + /** + * Returns a parameter by name if exists + * @since 2.1 + */ + default Optional getParameter(String parameterName) { + return Optional.ofNullable(getPositionOfParameter(parameterName)) + .map(getParameters()::get); + } + + /** + * Returns the list of annotations specified for this query + * + * @return a non-null, but possibly empty list of annotations + */ + public List getAllAnnotations(); + + /** + * Returns the list of annotations with a specified name + * + * @param annotationName + * @return a non-null, but possibly empty list of annotations + */ + public List getAnnotationsByName(String annotationName); + + /** + * Returns the first annotation with a specified name + * + * @since 2.0 + */ + public Optional getFirstAnnotationByName(String annotationName); + + /** + * Returns the visibility information about the query. + * @since 2.0 + */ + public PVisibility getVisibility(); + + /** + * Returns the non-qualified name of the query. By default this means returning the qualified name after the last + * '.' character. + * + * @since 2.0 + */ + public default String getSimpleName() { + return PQueries.calculateSimpleName(getFullyQualifiedName()); + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PVisibility.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PVisibility.java new file mode 100644 index 00000000..7cb312bd --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/PVisibility.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.queries; + +/** + * @author Zoltan Ujhelyi + * @since 2.0 + * + */ +public enum PVisibility { + + /** + * A public (default) visibility means a pattern can be called at any time. + */ + PUBLIC, + /** + * A private query is not expected to be called directly, only by a different query matcher. + */ + PRIVATE, + /** + * A query that is only used inside a single caller query and is not visible outside its container query. Such + * patterns must also fulfill the following additional constraints: + * + *

      + *
    • An embedded query must have only a single body.
    • + *
    • An embedded query must not be recursice.
    • + *
    + */ + EMBEDDED + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/QueryInitializationException.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/QueryInitializationException.java new file mode 100644 index 00000000..470d7287 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/queries/QueryInitializationException.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.queries; + +import tools.refinery.viatra.runtime.matchers.planning.QueryProcessingException; + +/** + * Represent an exception that occurred while initializing the specification of a query. + * @author Bergmann Gabor + * @since 0.9 + * + */ +public class QueryInitializationException extends QueryProcessingException { + + public QueryInitializationException(String message, String[] context, String shortMessage, Object patternDescription, + Throwable cause) { + super(message, context, shortMessage, patternDescription, cause); + } + + public QueryInitializationException(String message, String[] context, String shortMessage, Object patternDescription) { + super(message, context, shortMessage, patternDescription); + } + + private static final long serialVersionUID = 9106033062252951489L; + + + + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/AbstractRewriterTraceSource.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/AbstractRewriterTraceSource.java new file mode 100644 index 00000000..276b2b42 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/AbstractRewriterTraceSource.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Grill Balázs, IncQueryLabs + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.rewriters; + +import java.util.Objects; + +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PTraceable; + +/** + * @since 1.6 + * + */ +public class AbstractRewriterTraceSource { + + private IRewriterTraceCollector traceCollector = NopTraceCollector.INSTANCE; + + public void setTraceCollector(IRewriterTraceCollector traceCollector) { + this.traceCollector = Objects.requireNonNull(traceCollector); + } + + public IPTraceableTraceProvider getTraces() { + return traceCollector; + } + + protected IRewriterTraceCollector getTraceCollector() { + return traceCollector; + } + + /** + * Mark the given derivative to be originated from the given original constraint. + * @since 1.6 + */ + protected void addTrace(PTraceable original, PTraceable derivative){ + traceCollector.addTrace(original, derivative); + } + + /** + * Indicate that the given derivative is removed from the resulting query, thus its trace + * information should be removed also. + * @since 1.6 + */ + protected void derivativeRemoved(PConstraint derivative, IDerivativeModificationReason reason){ + traceCollector.derivativeRemoved(derivative, reason); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/ConstraintRemovalReason.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/ConstraintRemovalReason.java new file mode 100644 index 00000000..237a762d --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/ConstraintRemovalReason.java @@ -0,0 +1,23 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Grill Balázs, IncQueryLabs + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.rewriters; + +/** + * Common reasons for removing constraint through rewriters + * + * @noreference This enum is not intended to be referenced by clients. + */ +public enum ConstraintRemovalReason implements IDerivativeModificationReason { + + MOOT_EQUALITY, + WEAK_INEQUALITY_SELF_LOOP, + TYPE_SUBSUMED, + DUPLICATE + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/DefaultFlattenCallPredicate.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/DefaultFlattenCallPredicate.java new file mode 100644 index 00000000..3b5d7390 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/DefaultFlattenCallPredicate.java @@ -0,0 +1,23 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Marton Bur, Zoltan Ujhelyi, Akos Horvath, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.rewriters; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.PositivePatternCall; + +/** + * @author Marton Bur + * + */ +public class DefaultFlattenCallPredicate implements IFlattenCallPredicate { + + @Override + public boolean shouldFlatten(PositivePatternCall positivePatternCall) { + return true; + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/FlattenerCopier.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/FlattenerCopier.java new file mode 100644 index 00000000..06b8d372 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/FlattenerCopier.java @@ -0,0 +1,129 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Marton Bur, Akos Horvath, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.rewriters; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.Equality; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.ExportedParameter; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.ExpressionEvaluation; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.PositivePatternCall; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; + +/** + * This rewriter class can add new equality constraints to the copied body + * + * @author Marton Bur + * + */ +class FlattenerCopier extends PBodyCopier { + + private final Map calls; + + private static class CallInformation { + final PBody body; + final Map variableMapping; + + private CallInformation(PBody body) { + this.body = body; + this.variableMapping = new HashMap<>(); + } + } + + public FlattenerCopier(PQuery query, Map callsToFlatten) { + super(query); + this.calls = callsToFlatten.entrySet().stream().collect(Collectors.toMap(Entry::getKey, entry -> new CallInformation(entry.getValue()))); + } + + protected void copyVariable(PositivePatternCall contextPatternCall, PVariable variable, String newName) { + PVariable newPVariable = body.getOrCreateVariableByName(newName); + calls.get(contextPatternCall).variableMapping.put(variable, newPVariable); + variableMapping.put(variable, newPVariable); + } + + /** + * Merge all variables and constraints from the body called through the given pattern call to a target body. If + * multiple bodies are merged into a single one, use the renamer and filter options to avoid collisions. + * + * @param sourceBody + * @param namingTool + * @param filter + */ + public void mergeBody(PositivePatternCall contextPatternCall, IVariableRenamer namingTool, + IConstraintFilter filter) { + + PBody sourceBody = calls.get(contextPatternCall).body; + + // Copy variables + Set allVariables = sourceBody.getAllVariables(); + for (PVariable pVariable : allVariables) { + if (pVariable.isUnique()) { + copyVariable(contextPatternCall, pVariable, + namingTool.createVariableName(pVariable, sourceBody.getPattern())); + } + } + + // Copy constraints which are not filtered + Set constraints = sourceBody.getConstraints(); + for (PConstraint pConstraint : constraints) { + if (!(pConstraint instanceof ExportedParameter) && !filter.filter(pConstraint)) { + copyConstraint(pConstraint); + } + } + } + + @Override + protected void copyPositivePatternCallConstraint(PositivePatternCall positivePatternCall) { + + if (!calls.containsKey(positivePatternCall)) { + // If the call was not flattened, copy the constraint + super.copyPositivePatternCallConstraint(positivePatternCall); + } else { + PBody calledBody = Objects.requireNonNull(calls.get(positivePatternCall).body); + Preconditions.checkArgument(positivePatternCall.getReferredQuery().equals(calledBody.getPattern())); + + List symbolicParameters = calledBody.getSymbolicParameterVariables(); + Object[] elements = positivePatternCall.getVariablesTuple().getElements(); + for (int i = 0; i < elements.length; i++) { + // Create equality constraints between the caller PositivePatternCall and the corresponding body + // parameter variables + createEqualityConstraint((PVariable) elements[i], symbolicParameters.get(i), positivePatternCall); + } + + } + } + + private void createEqualityConstraint(PVariable pVariable1, PVariable pVariable2, + PositivePatternCall contextPatternCall) { + PVariable who = variableMapping.get(pVariable1); + PVariable withWhom = calls.get(contextPatternCall).variableMapping.get(pVariable2); + addTrace(contextPatternCall, new Equality(body, who, withWhom)); + } + + @Override + protected void copyExpressionEvaluationConstraint(final ExpressionEvaluation expressionEvaluation) { + Map variableMapping = this.variableMapping.entrySet().stream() + .filter(input -> expressionEvaluation.getPSystem().getAllVariables().contains(input.getKey())) + .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); + + PVariable mappedOutputVariable = variableMapping.get(expressionEvaluation.getOutputVariable()); + addTrace(expressionEvaluation, new ExpressionEvaluation(body, new VariableMappingExpressionEvaluatorWrapper(expressionEvaluation.getEvaluator(), variableMapping), mappedOutputVariable, expressionEvaluation.isUnwinding())); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IConstraintFilter.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IConstraintFilter.java new file mode 100644 index 00000000..518b9c64 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IConstraintFilter.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Zoltan Ujhelyi, Marton Bur, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.rewriters; + +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.ExportedParameter; + +/** + * Helper interface to exclude constraints from PBody copy processes + * + * @author Marton Bur + * + */ +public interface IConstraintFilter { + /** + * Returns true, if the given constraint should be filtered (thus should not be copied) + * + * @param constraint + * to check + * @return true, if the constraint should be filtered + */ + boolean filter(PConstraint constraint); + + public static class ExportedParameterFilter implements IConstraintFilter { + + @Override + public boolean filter(PConstraint constraint) { + return constraint instanceof ExportedParameter; + } + + } + + public static class AllowAllFilter implements IConstraintFilter { + + @Override + public boolean filter(PConstraint constraint) { + // Nothing is filtered + return false; + } + + } +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IDerivativeModificationReason.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IDerivativeModificationReason.java new file mode 100644 index 00000000..dbd6a78d --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IDerivativeModificationReason.java @@ -0,0 +1,19 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Grill Balázs, IncQueryLabs + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.rewriters; + +/** + * This is a role indication interface, implementations may provide a reason about + * why a modification is made during PQuery normalization. + * @since 1.6 + * + */ +public interface IDerivativeModificationReason { + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IFlattenCallPredicate.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IFlattenCallPredicate.java new file mode 100644 index 00000000..7e224e98 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IFlattenCallPredicate.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Marton Bur, Zoltan Ujhelyi, Akos Horvath, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.rewriters; + +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.PositivePatternCall; + + +/** + * Interface used by the PQueryFlattener to decide which positive pattern calls to flatten + * + * @author Marton Bur + * + */ +public interface IFlattenCallPredicate { + + /** + * Decides whether the called query by the pattern call should be flattened into the caller or not. + * + * @param positivePatternCall + * the pattern call + * @return true if the call should be flattened + */ + boolean shouldFlatten(PositivePatternCall positivePatternCall); + + /** + * Flattens only if all operand predicates vote for flattening. + * @author Gabor Bergmann + * @since 2.1 + */ + public static class And implements IFlattenCallPredicate { + private IFlattenCallPredicate[] operands; + public And(IFlattenCallPredicate... operands) { + this.operands = operands; + } + + @Override + public boolean shouldFlatten(PositivePatternCall positivePatternCall) { + for (IFlattenCallPredicate operand : operands) { + if (!operand.shouldFlatten(positivePatternCall)) return false; + } + return true; + } + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IPTraceableTraceProvider.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IPTraceableTraceProvider.java new file mode 100644 index 00000000..84da4d1b --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IPTraceableTraceProvider.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Grill Balázs, IncQueryLabs + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.rewriters; + +import java.util.stream.Stream; + +import tools.refinery.viatra.runtime.matchers.psystem.PTraceable; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; + +/** + * This interface provides methods to trace the {@link PTraceable}s of a transformed {@link PQuery} produced by + * a {@link PDisjunctionRewriter}. In case the associated rewriter is a composite (a.k.a. {@link PDisjunctionRewriterCacher}), + * this trace provider handles traces end-to-end, hiding all the intermediate transformation steps. + * + * @since 1.6 + * @noimplement This interface is not intended to be implemented by clients. + */ +public interface IPTraceableTraceProvider { + + /** + * Find and return the canonical {@link PTraceable}s in the original query which are the sources of the given derivative + * {@link PTraceable} according to the transformation. + * + * @param derivative a {@link PTraceable} which is contained by the {@link PQuery} produced by the associated rewriter + * @since 2.0 + */ + public Stream getCanonicalTraceables(PTraceable derivative); + + /** + * Find and return the {@link PTraceable}s in the rewritten query which are the destinations of the given source + * {@link PTraceable} according to the transformation. + * + * @param source a {@link PTraceable} which is contained by a {@link PQuery} before rewriting + * @since 2.0 + */ + public Stream getRewrittenTraceables(PTraceable source); + + /** + * Returns whether the given traceable element has been removed by every rewriter for a reason. + */ + public boolean isRemoved(PTraceable traceable); + + /** + * Returns the reasons for which the traceable element has been removed by the rewriters. + * @return the reasons of removal during rewriting + * @since 2.0 + */ + public Stream getRemovalReasons(PTraceable traceable); +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IRewriterTraceCollector.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IRewriterTraceCollector.java new file mode 100644 index 00000000..70771ea7 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IRewriterTraceCollector.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Grill Balázs, IncQueryLabs + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.rewriters; + +import tools.refinery.viatra.runtime.matchers.psystem.PTraceable; + +/** + * This is the internal API of {@link IPTraceableTraceProvider} expected to be used by + * copier and rewriter implementations. + * + * @since 1.6 + * @noreference This interface is not intended to be referenced by clients. + */ +public interface IRewriterTraceCollector extends IPTraceableTraceProvider { + + /** + * Mark the given derivative to be originated from the given original constraint. + */ + public void addTrace(PTraceable origin, PTraceable derivative); + + /** + * Indicate that the given derivative is removed from the resulting query, thus its trace + * information should be removed also. + */ + public void derivativeRemoved(PTraceable derivative, IDerivativeModificationReason reason); + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IVariableRenamer.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IVariableRenamer.java new file mode 100644 index 00000000..ce446e0d --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/IVariableRenamer.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Zoltan Ujhelyi, Marton Bur, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.rewriters; + +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; + +/** + * Helper interface to ease the naming of the new variables during flattening + * + * @author Marton Bur + * + */ +public interface IVariableRenamer { + /** + * Creates a variable name based on a given variable and a given query. It only creates a String, doesn't set + * anything. + * + * @param pVariable + * @param query + * @return the new variable name as a String + */ + String createVariableName(PVariable pVariable, PQuery query); + + public class SameName implements IVariableRenamer { + @Override + public String createVariableName(PVariable pVariable, PQuery query) { + return pVariable.getName(); + } + } + + public class HierarchicalName implements IVariableRenamer { + + private int callCount; + + public void setCallCount(int callCount) { + this.callCount = callCount; + } + + @Override + public String createVariableName(PVariable pVariable, PQuery query) { + // make sure to keep the "_" prefix before anonymous variables + String newVarName = getShortName(query) + "<" + callCount + ">" + "_" + pVariable.getName(); + return pVariable.getName().startsWith("_") ? "_" + newVarName : newVarName ; + } + + private String getShortName(PQuery query) { + String fullyQualifiedName = query.getFullyQualifiedName(); + int beginIndex = fullyQualifiedName.lastIndexOf('.') + 1; + return fullyQualifiedName.substring(beginIndex); + } + } +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/MappingTraceCollector.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/MappingTraceCollector.java new file mode 100644 index 00000000..7429fc60 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/MappingTraceCollector.java @@ -0,0 +1,135 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Grill Balázs, IncQueryLabs + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.rewriters; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import tools.refinery.viatra.runtime.matchers.psystem.PTraceable; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType; +import tools.refinery.viatra.runtime.matchers.util.IMemoryView; +import tools.refinery.viatra.runtime.matchers.util.IMultiLookup; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; + +/** + * Multimap-based implementation to contain and query traces + * + * @since 1.6 + * + */ +public class MappingTraceCollector implements IRewriterTraceCollector { + + /** + * Traces from derivative to original + */ + private final IMultiLookup traces = CollectionsFactory.createMultiLookup(Object.class, MemoryType.SETS, Object.class); + + /** + * Traces from original to derivative + */ + private final IMultiLookup inverseTraces = CollectionsFactory.createMultiLookup(Object.class, MemoryType.SETS, Object.class); + + /** + * Reasons for removing {@link PTraceable}s + */ + private final Map removals = new HashMap<>(); + + /** + * Decides whether {@link PTraceable} is removed + */ + private final Predicate removed = removals::containsKey; + + /** + * @since 2.0 + */ + @Override + public Stream getCanonicalTraceables(PTraceable derivative) { + return findTraceEnds(derivative, traces).stream(); + } + + /** + * @since 2.0 + */ + @Override + public Stream getRewrittenTraceables(PTraceable source) { + return findTraceEnds(source, inverseTraces).stream(); + } + + /** + * Returns the end of trace chains starting from the given {@link PTraceable} along the given trace edges. + */ + private Set findTraceEnds(PTraceable traceable, IMultiLookup traceRecords) { + if (traceable instanceof PQuery) { // PQueries are preserved + return Collections.singleton(traceable); + } + Set visited = new HashSet<>(); + Set result = new HashSet<>(); + Queue queue = new LinkedList<>(); + queue.add(traceable); + while(!queue.isEmpty()){ + PTraceable aDerivative = queue.poll(); + // Track visited elements to avoid infinite loop via directed cycles in traces + visited.add(aDerivative); + IMemoryView nextOrigins = traceRecords.lookup(aDerivative); + if (nextOrigins == null){ + // End of trace chain + result.add(aDerivative); + } else { + // Follow traces + for(PTraceable nextOrigin : nextOrigins){ + if (!visited.contains(nextOrigin)){ + queue.add(nextOrigin); + } + } + } + } + return result; + } + + @Override + public void addTrace(PTraceable original, PTraceable derivative){ + traces.addPairOrNop(derivative, original); + inverseTraces.addPairOrNop(original, derivative); + // Even if this element was marked as removed earlier, now we replace it with another constraint! + removals.remove(original); + } + + @Override + public void derivativeRemoved(PTraceable derivative, IDerivativeModificationReason reason){ + Preconditions.checkState(!removals.containsKey(derivative), "Traceable %s removed multiple times", derivative); + // XXX the derivative must not be removed from the trace chain, as some rewriters, e.g. the normalizer keeps trace links to deleted elements + if (!inverseTraces.lookupExists(derivative)) { + // If there already exists a trace link, this removal means an update + removals.put(derivative, reason); + } + } + + @Override + public boolean isRemoved(PTraceable traceable) { + return getRewrittenTraceables(traceable).allMatch(removed); + } + + /** + * @since 2.0 + */ + @Override + public Stream getRemovalReasons(PTraceable traceable) { + return getRewrittenTraceables(traceable).filter(removed).map(removals::get); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/NeverFlattenCallPredicate.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/NeverFlattenCallPredicate.java new file mode 100644 index 00000000..96c0b205 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/NeverFlattenCallPredicate.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Grill Balázs, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.rewriters; + +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.PositivePatternCall; + +/** + * @author Grill Balázs + * @since 1.4 + * + */ +public class NeverFlattenCallPredicate implements IFlattenCallPredicate { + + + @Override + public boolean shouldFlatten(PositivePatternCall positivePatternCall) { + return false; + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/NopTraceCollector.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/NopTraceCollector.java new file mode 100644 index 00000000..15cf577e --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/NopTraceCollector.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Grill Balázs, IncQueryLabs + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.rewriters; + +import java.util.stream.Stream; + +import tools.refinery.viatra.runtime.matchers.psystem.PTraceable; + +/** + * This implementation does not store any traces and scales to NOP for every traceability feature. + * @since 1.6 + * + */ +public class NopTraceCollector implements IRewriterTraceCollector { + + public static final IRewriterTraceCollector INSTANCE = new NopTraceCollector(); + + private NopTraceCollector() { + // Private constructor to force using the common instance + } + + /** + * @since 2.0 + */ + @Override + public Stream getCanonicalTraceables(PTraceable derivative) { + return Stream.empty(); + } + + /** + * @since 2.0 + */ + @Override + public Stream getRewrittenTraceables(PTraceable source) { + return Stream.empty(); + } + + + @Override + public void addTrace(PTraceable origin, PTraceable derivative) { + // ignored + } + + @Override + public void derivativeRemoved(PTraceable derivative, IDerivativeModificationReason reason) { + // ignored + } + + @Override + public boolean isRemoved(PTraceable traceable) { + return false; + } + + /** + * @since 2.0 + */ + @Override + public Stream getRemovalReasons(PTraceable traceable) { + return Stream.empty(); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PBodyCopier.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PBodyCopier.java new file mode 100644 index 00000000..e66c4eea --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PBodyCopier.java @@ -0,0 +1,306 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Marton Bur, Akos Horvath, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.rewriters; + +import tools.refinery.viatra.runtime.matchers.planning.QueryProcessingException; +import tools.refinery.viatra.runtime.matchers.psystem.EnumerablePConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.*; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.*; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.psystem.rewriters.IConstraintFilter.AllowAllFilter; +import tools.refinery.viatra.runtime.matchers.psystem.rewriters.IVariableRenamer.SameName; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * This class can create a new PBody for a PQuery. The result body contains a copy of given variables and constraints. + * + * @author Marton Bur + * + */ +public class PBodyCopier extends AbstractRewriterTraceSource { + + /** + * The created body + */ + protected PBody body; + /** + * Mapping between the original and the copied variables + */ + protected Map variableMapping = new HashMap<>(); + + public Map getVariableMapping() { + return variableMapping; + } + + /** + * @since 1.6 + */ + public PBodyCopier(PBody body, IRewriterTraceCollector traceCollector) { + this.body = new PBody(body.getPattern()); + setTraceCollector(traceCollector); + + // do the actual copying + mergeBody(body); + } + + /** + * @since 1.6 + */ + public PBodyCopier(PQuery query) { + this.body = new PBody(query); + } + + public void mergeBody(PBody sourceBody) { + mergeBody(sourceBody, new SameName(), new AllowAllFilter()); + } + + /** + * Merge all variables and constraints from a source body to a target body. If multiple bodies are merged into a + * single one, use the renamer and filter options to avoid collisions. + */ + public void mergeBody(PBody sourceBody, IVariableRenamer namingTool, IConstraintFilter filter) { + + // Copy variables + Set allVariables = sourceBody.getAllVariables(); + for (PVariable pVariable : allVariables) { + if (pVariable.isUnique()) { + copyVariable(pVariable, namingTool.createVariableName(pVariable, sourceBody.getPattern())); + } + } + + // Copy exported parameters + this.body.setSymbolicParameters(sourceBody.getSymbolicParameters().stream() + .map(this::copyExportedParameterConstraint).collect(Collectors.toList())); + + // Copy constraints which are not filtered + Set constraints = sourceBody.getConstraints(); + for (PConstraint pConstraint : constraints) { + if (!(pConstraint instanceof ExportedParameter) && !filter.filter(pConstraint)) { + copyConstraint(pConstraint); + } + } + + // Add trace between original and copied body + addTrace(sourceBody, body); + } + + protected void copyVariable(PVariable variable, String newName) { + PVariable newPVariable = body.getOrCreateVariableByName(newName); + variableMapping.put(variable, newPVariable); + } + + /** + * Returns the body with the copied variables and constraints. The returned body is still uninitialized. + */ + public PBody getCopiedBody() { + return body; + } + + protected void copyConstraint(PConstraint constraint) { + if (constraint instanceof ExportedParameter) { + copyExportedParameterConstraint((ExportedParameter) constraint); + } else if (constraint instanceof Equality) { + copyEqualityConstraint((Equality) constraint); + } else if (constraint instanceof Inequality) { + copyInequalityConstraint((Inequality) constraint); + } else if (constraint instanceof TypeConstraint) { + copyTypeConstraint((TypeConstraint) constraint); + } else if (constraint instanceof TypeFilterConstraint) { + copyTypeFilterConstraint((TypeFilterConstraint) constraint); + } else if (constraint instanceof ConstantValue) { + copyConstantValueConstraint((ConstantValue) constraint); + } else if (constraint instanceof PositivePatternCall) { + copyPositivePatternCallConstraint((PositivePatternCall) constraint); + } else if (constraint instanceof NegativePatternCall) { + copyNegativePatternCallConstraint((NegativePatternCall) constraint); + } else if (constraint instanceof BinaryTransitiveClosure) { + copyBinaryTransitiveClosureConstraint((BinaryTransitiveClosure) constraint); + } else if (constraint instanceof RepresentativeElectionConstraint) { + copyRepresentativeElectionConstraint((RepresentativeElectionConstraint) constraint); + } else if (constraint instanceof RelationEvaluation) { + copyRelationEvaluationConstraint((RelationEvaluation) constraint); + } else if (constraint instanceof BinaryReflexiveTransitiveClosure) { + copyBinaryReflexiveTransitiveClosureConstraint((BinaryReflexiveTransitiveClosure) constraint); + } else if (constraint instanceof PatternMatchCounter) { + copyPatternMatchCounterConstraint((PatternMatchCounter) constraint); + } else if (constraint instanceof AggregatorConstraint) { + copyAggregatorConstraint((AggregatorConstraint) constraint); + } else if (constraint instanceof ExpressionEvaluation) { + copyExpressionEvaluationConstraint((ExpressionEvaluation) constraint); + } else { + throw new QueryProcessingException("Unknown PConstraint {0} encountered while copying PBody", + new String[] { constraint.getClass().getName() }, "Unknown PConstraint", body.getPattern()); + } + } + + protected ExportedParameter copyExportedParameterConstraint(ExportedParameter exportedParameter) { + PVariable mappedPVariable = variableMapping.get(exportedParameter.getParameterVariable()); + PParameter parameter = exportedParameter.getPatternParameter(); + ExportedParameter newExportedParameter; + newExportedParameter = new ExportedParameter(body, mappedPVariable, parameter); + body.getSymbolicParameters().add(newExportedParameter); + addTrace(exportedParameter, newExportedParameter); + return newExportedParameter; + } + + protected void copyEqualityConstraint(Equality equality) { + PVariable who = equality.getWho(); + PVariable withWhom = equality.getWithWhom(); + addTrace(equality, new Equality(body, variableMapping.get(who), variableMapping.get(withWhom))); + } + + protected void copyInequalityConstraint(Inequality inequality) { + PVariable who = inequality.getWho(); + PVariable withWhom = inequality.getWithWhom(); + addTrace(inequality, new Inequality(body, variableMapping.get(who), variableMapping.get(withWhom))); + } + + protected void copyTypeConstraint(TypeConstraint typeConstraint) { + PVariable[] mappedVariables = extractMappedVariables(typeConstraint); + Tuple variablesTuple = Tuples.flatTupleOf((Object[]) mappedVariables); + addTrace(typeConstraint, new TypeConstraint(body, variablesTuple, typeConstraint.getSupplierKey())); + } + + protected void copyTypeFilterConstraint(TypeFilterConstraint typeConstraint) { + PVariable[] mappedVariables = extractMappedVariables(typeConstraint); + Tuple variablesTuple = Tuples.flatTupleOf((Object[]) mappedVariables); + addTrace(typeConstraint, new TypeFilterConstraint(body, variablesTuple, typeConstraint.getInputKey())); + } + + protected void copyConstantValueConstraint(ConstantValue constantValue) { + PVariable pVariable = (PVariable) constantValue.getVariablesTuple().getElements()[0]; + addTrace(constantValue, + new ConstantValue(body, variableMapping.get(pVariable), constantValue.getSupplierKey())); + } + + protected void copyPositivePatternCallConstraint(PositivePatternCall positivePatternCall) { + PVariable[] mappedVariables = extractMappedVariables(positivePatternCall); + Tuple variablesTuple = Tuples.flatTupleOf((Object[]) mappedVariables); + addTrace(positivePatternCall, + new PositivePatternCall(body, variablesTuple, positivePatternCall.getReferredQuery())); + } + + protected void copyNegativePatternCallConstraint(NegativePatternCall negativePatternCall) { + PVariable[] mappedVariables = extractMappedVariables(negativePatternCall); + Tuple variablesTuple = Tuples.flatTupleOf((Object[]) mappedVariables); + addTrace(negativePatternCall, + new NegativePatternCall(body, variablesTuple, negativePatternCall.getReferredQuery())); + } + + protected void copyBinaryTransitiveClosureConstraint(BinaryTransitiveClosure binaryTransitiveClosure) { + PVariable[] mappedVariables = extractMappedVariables(binaryTransitiveClosure); + Tuple variablesTuple = Tuples.flatTupleOf((Object[]) mappedVariables); + addTrace(binaryTransitiveClosure, + new BinaryTransitiveClosure(body, variablesTuple, binaryTransitiveClosure.getReferredQuery())); + } + + protected void copyRepresentativeElectionConstraint(RepresentativeElectionConstraint constraint) { + var mappedVariables = extractMappedVariables(constraint); + var variablesTuple = Tuples.flatTupleOf((Object[]) mappedVariables); + addTrace(constraint, new RepresentativeElectionConstraint(body, variablesTuple, constraint.getReferredQuery(), + constraint.getConnectivity())); + } + + /** + * @since 2.8 + */ + protected void copyRelationEvaluationConstraint(RelationEvaluation relationEvaluation) { + PVariable[] mappedVariables = extractMappedVariables(relationEvaluation); + Tuple variablesTuple = Tuples.flatTupleOf((Object[]) mappedVariables); + addTrace(relationEvaluation, new RelationEvaluation(body, variablesTuple, relationEvaluation.getReferredQueries(), + relationEvaluation.getEvaluator())); + } + + /** + * @since 2.0 + */ + protected void copyBinaryReflexiveTransitiveClosureConstraint( + BinaryReflexiveTransitiveClosure binaryReflexiveTransitiveClosure) { + PVariable[] mappedVariables = extractMappedVariables(binaryReflexiveTransitiveClosure); + Tuple variablesTuple = Tuples.flatTupleOf((Object[]) mappedVariables); + addTrace(binaryReflexiveTransitiveClosure, + new BinaryReflexiveTransitiveClosure(body, variablesTuple, + binaryReflexiveTransitiveClosure.getReferredQuery(), + binaryReflexiveTransitiveClosure.getUniverseType())); + } + + protected void copyPatternMatchCounterConstraint(PatternMatchCounter patternMatchCounter) { + PVariable[] mappedVariables = extractMappedVariables(patternMatchCounter); + PVariable mappedResultVariable = variableMapping.get(patternMatchCounter.getResultVariable()); + Tuple variablesTuple = Tuples.flatTupleOf((Object[]) mappedVariables); + addTrace(patternMatchCounter, new PatternMatchCounter(body, variablesTuple, + patternMatchCounter.getReferredQuery(), mappedResultVariable)); + } + + /** + * @since 1.4 + */ + protected void copyAggregatorConstraint(AggregatorConstraint constraint) { + PVariable[] mappedVariables = extractMappedVariables(constraint); + PVariable mappedResultVariable = variableMapping.get(constraint.getResultVariable()); + Tuple variablesTuple = Tuples.flatTupleOf((Object[]) mappedVariables); + addTrace(constraint, new AggregatorConstraint(constraint.getAggregator(), body, variablesTuple, + constraint.getReferredQuery(), mappedResultVariable, constraint.getAggregatedColumn())); + } + + protected void copyExpressionEvaluationConstraint(ExpressionEvaluation expressionEvaluation) { + PVariable mappedOutputVariable = variableMapping.get(expressionEvaluation.getOutputVariable()); + addTrace(expressionEvaluation, new ExpressionEvaluation(body, + new VariableMappingExpressionEvaluatorWrapper(expressionEvaluation.getEvaluator(), variableMapping), + mappedOutputVariable, expressionEvaluation.isUnwinding())); + } + + /** + * For positive pattern calls + * + * @param positivePatternCall + * @return the mapped variables to the pattern's parameters + */ + protected PVariable[] extractMappedVariables(EnumerablePConstraint enumerablePConstraint) { + Object[] pVariables = enumerablePConstraint.getVariablesTuple().getElements(); + return mapVariableList(pVariables); + } + + /** + * For negative and count pattern calls. + * + * @param patternMatchCounter + * @return the mapped variables to the pattern's parameters + */ + private PVariable[] extractMappedVariables(PatternCallBasedDeferred patternCallBasedDeferred) { + Object[] pVariables = patternCallBasedDeferred.getActualParametersTuple().getElements(); + return mapVariableList(pVariables); + } + + /** + * For type filters. + */ + private PVariable[] extractMappedVariables(TypeFilterConstraint typeFilterConstraint) { + Object[] pVariables = typeFilterConstraint.getVariablesTuple().getElements(); + return mapVariableList(pVariables); + } + + private PVariable[] mapVariableList(Object[] pVariables) { + List list = new ArrayList(); + for (int i = 0; i < pVariables.length; i++) { + PVariable mappedVariable = variableMapping.get(pVariables[i]); + list.add(mappedVariable); + } + return list.toArray(new PVariable[0]); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PBodyNormalizer.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PBodyNormalizer.java new file mode 100644 index 00000000..90943129 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PBodyNormalizer.java @@ -0,0 +1,310 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.psystem.rewriters; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.planning.QueryProcessingException; +import tools.refinery.viatra.runtime.matchers.planning.helpers.TypeHelper; +import tools.refinery.viatra.runtime.matchers.psystem.ITypeConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.ITypeInfoProviderConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.TypeJudgement; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.Equality; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.Inequality; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PDisjunction; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery.PQueryStatus; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; + +/** + * A disjunction rewriter for creating a normalized form of specification, unifying variables and running basic sanity + * checks. This rewriter does not copy but modifies directly the original specification, requiring a mutable + * disjunction. + * + * @author Gabor Bergmann + * + */ +public class PBodyNormalizer extends PDisjunctionRewriter { + + private IQueryMetaContext context; + + public PBodyNormalizer(IQueryMetaContext context) { + this.context = context; + } + + /** + * Returns whether unary constraint elimination is enabled. This behavior can be customized by creating a subclass + * with a custom implementation. + * + * @since 1.6 + */ + protected boolean shouldCalculateImpliedTypes(PQuery query) { + return true; + } + + /** + * Returns whether 'weakened alternative' suggestions of the context shall be expanded as additional PConstraints. + * This behavior can be customized by creating a subclass + * with a custom implementation. + * + * @since 1.6 + */ + protected boolean shouldExpandWeakenedAlternatives(PQuery query) { + return false; + } + + @Override + public PDisjunction rewrite(PDisjunction disjunction) { + Set normalizedBodies = new LinkedHashSet<>(); + for (PBody body : disjunction.getBodies()) { + PBodyCopier copier = new PBodyCopier(body, getTraceCollector()); + PBody modifiedBody = copier.getCopiedBody(); + normalizeBody(modifiedBody); + normalizedBodies.add(modifiedBody); + modifiedBody.setStatus(PQueryStatus.OK); + } + return new PDisjunction(normalizedBodies); + } + + public void setContext(IQueryMetaContext context) { + this.context = context; + } + + /** + * Provides a normalized version of the pattern body. May return a different version than the original version if + * needed. + * + * @param body + */ + public PBody normalizeBody(PBody body) { + try { + return normalizeBodyInternal(body); + } catch (QueryProcessingException e) { + throw new RewriterException("Error during rewriting: {1}", new String[] { e.getMessage() }, + e.getShortMessage(), body.getPattern(), e); + } + } + + PBody normalizeBodyInternal(PBody body) { + // UNIFICATION AND WEAK INEQUALITY ELIMINATION + unifyVariablesAlongEqualities(body); + eliminateWeakInequalities(body); + removeMootEqualities(body); + + // ADDING WEAKENED ALTERNATIVES + if (shouldExpandWeakenedAlternatives(body.getPattern())) { + expandWeakenedAlternativeConstraints(body); + } + + // CONSTRAINT ELIMINATION WITH TYPE INFERENCE + if (shouldCalculateImpliedTypes(body.getPattern())) { + eliminateInferrableTypes(body, context); + } else { + // ELIMINATE DUPLICATE TYPE CONSTRAINTS + eliminateDuplicateTypeConstraints(body); + } + + + // PREVENTIVE CHECKS + checkSanity(body); + return body; + } + + private void removeMootEqualities(PBody body) { + Set equals = body.getConstraintsOfType(Equality.class); + for (Equality equality : equals) { + if (equality.isMoot()) { + equality.delete(); + derivativeRemoved(equality, ConstraintRemovalReason.MOOT_EQUALITY); + } + } + } + + /** + * Unifies allVariables along equalities so that they can be handled as one. + * + * @param body + */ + void unifyVariablesAlongEqualities(PBody body) { + Set equals = body.getConstraintsOfType(Equality.class); + for (Equality equality : equals) { + if (!equality.isMoot()) { + equality.getWho().unifyInto(equality.getWithWhom()); + } + } + } + + /** + * Eliminates weak inequalities if they are not substantiated. + * + * @param body + */ + void eliminateWeakInequalities(PBody body) { + for (Inequality inequality : body.getConstraintsOfType(Inequality.class)){ + if (inequality.isEliminable()){ + inequality.eliminateWeak(); + derivativeRemoved(inequality, ConstraintRemovalReason.WEAK_INEQUALITY_SELF_LOOP); + } + } + } + + /** + * Eliminates all type constraints that are inferrable from other constraints. + */ + void eliminateInferrableTypes(final PBody body, IQueryMetaContext context) { + Set subsumedByRetainedConstraints = new HashSet(); + LinkedList allTypeConstraints = new LinkedList(); + for (PConstraint pConstraint : body.getConstraints()) { + if (pConstraint instanceof ITypeConstraint) { + allTypeConstraints.add((ITypeConstraint) pConstraint); + } else if (pConstraint instanceof ITypeInfoProviderConstraint) { + // non-type constraints are all retained + final Set directJudgements = ((ITypeInfoProviderConstraint) pConstraint) + .getImpliedJudgements(context); + subsumedByRetainedConstraints = TypeHelper.typeClosure(subsumedByRetainedConstraints, directJudgements, + context); + } + } + Comparator eliminationOrder = (o1, o2) -> { + IInputKey type1 = o1.getEquivalentJudgement().getInputKey(); + IInputKey type2 = o2.getEquivalentJudgement().getInputKey(); + + int result = context.getSuggestedEliminationOrdering().compare(type1, type2); + return (result == 0) + ? PConstraint.COMPARE_BY_MONOTONOUS_ID.compare(o1, o2) + : result; + }; + + Collections.sort(allTypeConstraints, eliminationOrder); + Queue potentialConstraints = allTypeConstraints; // rename for better comprehension + + while (!potentialConstraints.isEmpty()) { + ITypeConstraint candidate = potentialConstraints.poll(); + + boolean isSubsumed = subsumedByRetainedConstraints.contains(candidate.getEquivalentJudgement()); + if (!isSubsumed) { + Set typeClosure = subsumedByRetainedConstraints; + for (ITypeConstraint subsuming : potentialConstraints) { // the remaining ones + final Set directJudgements = subsuming.getImpliedJudgements(context); + typeClosure = TypeHelper.typeClosure(typeClosure, directJudgements, context); + + if (typeClosure.contains(candidate.getEquivalentJudgement())) { + isSubsumed = true; + break; + } + } + } + if (isSubsumed) { // eliminated + candidate.delete(); + derivativeRemoved(candidate, ConstraintRemovalReason.TYPE_SUBSUMED); + } else { // retained + subsumedByRetainedConstraints = TypeHelper.typeClosure(subsumedByRetainedConstraints, + candidate.getImpliedJudgements(context), context); + } + } + } + + /** + * Inserts "weakened alternative" constraints suggested by the meta context that aid in coming up with a query plan. + */ + void expandWeakenedAlternativeConstraints(PBody body) { + Set allJudgements = new HashSet(); + Set newJudgementsToAdd = new HashSet(); + Queue judgementsToProcess = new LinkedList(); + Map> traceability = CollectionsFactory.createMap(); + + for (ITypeConstraint typeConstraint : body.getConstraintsOfType(ITypeConstraint.class)) { + TypeJudgement equivalentJudgement = typeConstraint.getEquivalentJudgement(); + judgementsToProcess.add(equivalentJudgement); + allJudgements.add(equivalentJudgement); + traceability.computeIfAbsent(equivalentJudgement, k-> new ArrayList<>()).add(typeConstraint); + } + + while (!judgementsToProcess.isEmpty()) { + TypeJudgement judgement = judgementsToProcess.poll(); + for (TypeJudgement alternativeJudgement : judgement.getWeakenedAlternativeJudgements(context)) { + if (allJudgements.add(alternativeJudgement)) { + newJudgementsToAdd.add(alternativeJudgement); + judgementsToProcess.add(alternativeJudgement); + traceability.merge( + alternativeJudgement, + traceability.getOrDefault(judgement, new ArrayList<>()), + (old,further) -> {old.addAll(further); return old;} + ); + } + } + } + + for (TypeJudgement typeJudgement : newJudgementsToAdd) { + PConstraint newConstraint = typeJudgement.createConstraintFor(body); + for (PConstraint source : traceability.getOrDefault(typeJudgement, Collections.emptyList())) { + addTrace(source, newConstraint); + } + } + } + + private Object getConstraintKey(PConstraint constraint) { + if (constraint instanceof ITypeConstraint) { + return ((ITypeConstraint) constraint).getEquivalentJudgement(); + } + // Do not check duplication for any other types + return constraint; + } + + void eliminateDuplicateTypeConstraints(PBody body) { + Map constraints = new HashMap<>(); + for (PConstraint constraint : body.getConstraints()) { + Object key = getConstraintKey(constraint); + // Retain first found instance of a constraint + if (!constraints.containsKey(key)) { + constraints.put(key, constraint); + } + } + + // Retain collected constraints, remove everything else + Iterator iterator = body.getConstraints().iterator(); + Collection toRetain = constraints.values(); + while(iterator.hasNext()){ + PConstraint next = iterator.next(); + if (!toRetain.contains(next)){ + derivativeRemoved(next, ConstraintRemovalReason.DUPLICATE); + iterator.remove(); + } + } + } + + /** + * Verifies the sanity of all constraints. Should be issued as a preventive check before layouting. + * + * @param body + * @throws RetePatternBuildException + */ + void checkSanity(PBody body) { + for (PConstraint pConstraint : body.getConstraints()) + pConstraint.checkSanity(); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PDisjunctionRewriter.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PDisjunctionRewriter.java new file mode 100644 index 00000000..c844ccf7 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PDisjunctionRewriter.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.rewriters; + +import tools.refinery.viatra.runtime.matchers.psystem.queries.PDisjunction; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; + +/** + * An abstract base class for creating alternative representations for PDisjunctions. + * @author Zoltan Ujhelyi + * + */ +public abstract class PDisjunctionRewriter extends AbstractRewriterTraceSource{ + + public abstract PDisjunction rewrite(PDisjunction disjunction); + + public PDisjunction rewrite(PQuery query) { + return rewrite(query.getDisjunctBodies()); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PDisjunctionRewriterCacher.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PDisjunctionRewriterCacher.java new file mode 100644 index 00000000..eb5422ca --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PDisjunctionRewriterCacher.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.rewriters; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.WeakHashMap; + +import tools.refinery.viatra.runtime.matchers.psystem.queries.PDisjunction; + +/** + * A rewriter that stores the previously computed results of a rewriter or a rewriter chain. + * + * @author Zoltan Ujhelyi + * @since 1.0 + */ +public class PDisjunctionRewriterCacher extends PDisjunctionRewriter { + + private final List rewriterChain; + private WeakHashMap cachedResults = + new WeakHashMap(); + + private void setupTraceCollectorInChain(){ + IRewriterTraceCollector collector = getTraceCollector(); + for(PDisjunctionRewriter rewriter: rewriterChain){ + rewriter.setTraceCollector(collector); + } + } + + public PDisjunctionRewriterCacher(PDisjunctionRewriter rewriter) { + rewriterChain = Collections.singletonList(rewriter); + } + + public PDisjunctionRewriterCacher(PDisjunctionRewriter... rewriters) { + rewriterChain = new ArrayList<>(Arrays.asList(rewriters)); + } + + public PDisjunctionRewriterCacher(List rewriterChain) { + this.rewriterChain = new ArrayList<>(rewriterChain); + } + + @Override + public PDisjunction rewrite(PDisjunction disjunction) { + if (!cachedResults.containsKey(disjunction)) { + PDisjunction rewritten = disjunction; + setupTraceCollectorInChain(); + for (PDisjunctionRewriter rewriter : rewriterChain) { + rewritten = rewriter.rewrite(rewritten); + } + + cachedResults.put(disjunction, rewritten); + } + return cachedResults.get(disjunction); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PQueryFlattener.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PQueryFlattener.java new file mode 100644 index 00000000..76311d8f --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/PQueryFlattener.java @@ -0,0 +1,253 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Marton Bur, Akos Horvath, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.rewriters; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.PositivePatternCall; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PDisjunction; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery.PQueryStatus; +import tools.refinery.viatra.runtime.matchers.psystem.rewriters.IConstraintFilter.AllowAllFilter; +import tools.refinery.viatra.runtime.matchers.psystem.rewriters.IConstraintFilter.ExportedParameterFilter; +import tools.refinery.viatra.runtime.matchers.psystem.rewriters.IVariableRenamer.HierarchicalName; +import tools.refinery.viatra.runtime.matchers.psystem.rewriters.IVariableRenamer.SameName; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; +import tools.refinery.viatra.runtime.matchers.util.Sets; + +/** + * This rewriter class holds the query flattening logic + * + * @author Marton Bur + * + */ +public class PQueryFlattener extends PDisjunctionRewriter { + + /** + * Utility function to produce the permutation of every possible mapping of values. + * + * @param values + * @return + */ + private static Set> permutation(Map> values) { + // An ordering of keys is defined here which will help restoring the appropriate values after the execution of + // the cartesian product + List keyList = new ArrayList<>(values.keySet()); + + // Produce list of value sets with the ordering defined by keyList + List> valuesList = new ArrayList>(keyList.size()); + for (K key : keyList) { + valuesList.add(values.get(key)); + } + + // Cartesian product will obey ordering of the list + Set> valueMappings = Sets.cartesianProduct(valuesList); + + // Build result + Set> result = new LinkedHashSet<>(); + for (List valueList : valueMappings) { + Map map = new HashMap<>(); + for (int i = 0; i < keyList.size(); i++) { + map.put(keyList.get(i), valueList.get(i)); + } + result.add(map); + } + + return result; + } + + private IFlattenCallPredicate flattenCallPredicate; + + public PQueryFlattener(IFlattenCallPredicate flattenCallPredicate) { + this.flattenCallPredicate = flattenCallPredicate; + } + + @Override + public PDisjunction rewrite(PDisjunction disjunction) { + PQuery query = disjunction.getQuery(); + + // Check for recursion + Set allReferredQueries = disjunction.getAllReferredQueries(); + for (PQuery referredQuery : allReferredQueries) { + if (referredQuery.getAllReferredQueries().contains(referredQuery)) { + throw new RewriterException("Recursive queries are not supported, can't flatten query named \"{1}\"", + new String[] { query.getFullyQualifiedName() }, "Unsupported recursive query", query); + } + } + + return this.doFlatten(disjunction); + } + + /** + * Return the list of dependencies (including the root) in chronological order + * + * @param rootDisjunction + * @return + */ + private List disjunctionDependencies(PDisjunction rootDisjunction) { + // Disjunctions are first collected into a list usign a depth-first approach, + // which can be iterated backwards while removing duplicates + Deque stack = new ArrayDeque<>(); + LinkedList list = new LinkedList<>(); + stack.push(rootDisjunction); + list.add(rootDisjunction); + + while (!stack.isEmpty()) { + PDisjunction disjunction = stack.pop(); + // Collect dependencies + for (PBody pBody : disjunction.getBodies()) { + for (PConstraint constraint : pBody.getConstraints()) { + if (constraint instanceof PositivePatternCall) { + PositivePatternCall positivePatternCall = (PositivePatternCall) constraint; + if (flattenCallPredicate.shouldFlatten(positivePatternCall)) { + // If the above preconditions meet, the call should be flattened + PDisjunction calledDisjunction = positivePatternCall.getReferredQuery().getDisjunctBodies(); + stack.push(calledDisjunction); + list.add(calledDisjunction); + } + } + } + } + } + + // Remove duplicates (keeping the last instance) and reverse order + Set visited = new HashSet(); + List result = new ArrayList(list.size()); + + list.descendingIterator().forEachRemaining(item -> { + if (!visited.contains(item)) { + result.add(item); + visited.add(item); + } + + }); + + return result; + } + + /** + * This function holds the actual flattening logic for a PQuery + * + * @param rootDisjunction + * to be flattened + * @return the flattened bodies of the pQuery + */ + private PDisjunction doFlatten(PDisjunction rootDisjunction) { + + Map> flatBodyMapping = new HashMap<>(); + + List dependencies = disjunctionDependencies(rootDisjunction); + + for (PDisjunction disjunction : dependencies) { + Set flatBodies = new LinkedHashSet<>(); + for (PBody body : disjunction.getBodies()) { + if (isFlatteningNeeded(body)) { + Map> flattenedBodies = new HashMap<>(); + for (PConstraint pConstraint : body.getConstraints()) { + + if (pConstraint instanceof PositivePatternCall) { + PositivePatternCall positivePatternCall = (PositivePatternCall) pConstraint; + if (flattenCallPredicate.shouldFlatten(positivePatternCall)) { + // If the above preconditions meet, do the flattening and return the disjoint bodies + PDisjunction calledDisjunction = positivePatternCall.getReferredQuery() + .getDisjunctBodies(); + + Set flattenedBodySet = flatBodyMapping.get(calledDisjunction); + Preconditions.checkArgument(!flattenedBodySet.isEmpty()); + flattenedBodies.put(positivePatternCall, flattenedBodySet); + } + } + } + flatBodies.addAll(createSetOfFlatPBodies(body, flattenedBodies)); + } else { + flatBodies.add(prepareFlatPBody(body)); + } + } + flatBodyMapping.put(disjunction, flatBodies); + } + + return new PDisjunction(rootDisjunction.getQuery(), flatBodyMapping.get(rootDisjunction)); + } + + /** + * Creates the flattened bodies based on the caller body and the called (and already flattened) disjunctions + * + * @param pBody + * the body to flatten + * @param flattenedDisjunctions + * the + * @param flattenedCalls + * @return + */ + private Set createSetOfFlatPBodies(PBody pBody, Map> flattenedCalls) { + PQuery pQuery = pBody.getPattern(); + + Set> conjunctedCalls = permutation(flattenedCalls); + + // The result set containing the merged conjuncted bodies + Set conjunctedBodies = new HashSet<>(); + + for (Map calledBodies : conjunctedCalls) { + FlattenerCopier copier = createBodyCopier(pQuery, calledBodies); + + int i = 0; + HierarchicalName hierarchicalNamingTool = new HierarchicalName(); + for (PositivePatternCall patternCall : calledBodies.keySet()) { + // Merge each called body + hierarchicalNamingTool.setCallCount(i++); + copier.mergeBody(patternCall, hierarchicalNamingTool, new ExportedParameterFilter()); + } + + // Merge the caller's constraints to the conjunct body + copier.mergeBody(pBody); + + PBody copiedBody = copier.getCopiedBody(); + copiedBody.setStatus(PQueryStatus.OK); + conjunctedBodies.add(copiedBody); + } + + return conjunctedBodies; + } + + private FlattenerCopier createBodyCopier(PQuery query, Map calledBodies) { + FlattenerCopier flattenerCopier = new FlattenerCopier(query, calledBodies); + flattenerCopier.setTraceCollector(getTraceCollector()); + return flattenerCopier; + } + + private PBody prepareFlatPBody(PBody pBody) { + PBodyCopier copier = createBodyCopier(pBody.getPattern(), Collections. emptyMap()); + copier.mergeBody(pBody, new SameName(), new AllowAllFilter()); + // the copying of the body here is necessary for only one containing PDisjunction can be assigned to a PBody + return copier.getCopiedBody(); + } + + private boolean isFlatteningNeeded(PBody pBody) { + // Check if the body contains positive pattern call AND if it should be flattened + for (PConstraint pConstraint : pBody.getConstraints()) { + if (pConstraint instanceof PositivePatternCall) { + return flattenCallPredicate.shouldFlatten((PositivePatternCall) pConstraint); + } + } + return false; + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/RewriterException.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/RewriterException.java new file mode 100644 index 00000000..d0fc286b --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/RewriterException.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.rewriters; + +import tools.refinery.viatra.runtime.matchers.psystem.queries.QueryInitializationException; + +/** + * An exception to wrap various issues during PDisjunction rewriting. + * @author Zoltan Ujhelyi + * + */ +public class RewriterException extends QueryInitializationException { + + private static final long serialVersionUID = -4703825954995497932L; + + public RewriterException(String message, String[] context, String shortMessage, Object patternDescription, + Throwable cause) { + super(message, context, shortMessage, patternDescription, cause); + } + + public RewriterException(String message, String[] context, String shortMessage, Object patternDescription) { + super(message, context, shortMessage, patternDescription); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/SurrogateQueryRewriter.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/SurrogateQueryRewriter.java new file mode 100644 index 00000000..71459558 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/SurrogateQueryRewriter.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.rewriters; + +import java.util.LinkedHashSet; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.surrogate.SurrogateQueryRegistry; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.PositivePatternCall; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.TypeConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PDisjunction; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery.PQueryStatus; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; + +/** + * @author Zoltan Ujhelyi + * + */ +public class SurrogateQueryRewriter extends PDisjunctionRewriter { + + @Override + public PDisjunction rewrite(PDisjunction disjunction) { + Set replacedBodies = new LinkedHashSet<>(); + for (PBody body : disjunction.getBodies()) { + PBodyCopier copier = new PBodyCopier(body, getTraceCollector()) { + + @Override + protected void copyTypeConstraint(TypeConstraint typeConstraint) { + PVariable[] mappedVariables = extractMappedVariables(typeConstraint); + Tuple variablesTuple = Tuples.flatTupleOf((Object[])mappedVariables); + final IInputKey supplierKey = typeConstraint.getSupplierKey(); + if(SurrogateQueryRegistry.instance().hasSurrogateQueryFQN(supplierKey)) { + PQuery surrogateQuery = SurrogateQueryRegistry.instance().getSurrogateQuery(supplierKey); + if (surrogateQuery == null) { + throw new IllegalStateException( + String.format("Surrogate query for feature %s not found", + supplierKey.getPrettyPrintableName())); + } + addTrace(typeConstraint, new PositivePatternCall(getCopiedBody(), variablesTuple, surrogateQuery)); + } else { + addTrace(typeConstraint, new TypeConstraint(getCopiedBody(), variablesTuple, supplierKey)); + } + } + }; + PBody modifiedBody = copier.getCopiedBody(); + replacedBodies.add(modifiedBody); + modifiedBody.setStatus(PQueryStatus.OK); + } + return new PDisjunction(replacedBodies); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/VariableMappingExpressionEvaluatorWrapper.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/VariableMappingExpressionEvaluatorWrapper.java new file mode 100644 index 00000000..10337979 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/psystem/rewriters/VariableMappingExpressionEvaluatorWrapper.java @@ -0,0 +1,88 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Grill Balázs, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.psystem.rewriters; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.psystem.IExpressionEvaluator; +import tools.refinery.viatra.runtime.matchers.psystem.IValueProvider; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; + +/** + * A wrapper for {@link IExpressionEvaluator} which is capable of correctly mapping variable names used by the + * expression. + * + * @author Grill Balázs + * + */ +class VariableMappingExpressionEvaluatorWrapper implements IExpressionEvaluator { + + private final IExpressionEvaluator wrapped; + private final Map variableMapping; + + public VariableMappingExpressionEvaluatorWrapper(IExpressionEvaluator wrapped, + Map variableMapping) { + + // Support to rewrap an already wrapped expression. + boolean rewrap = wrapped instanceof VariableMappingExpressionEvaluatorWrapper; + this.wrapped = rewrap ? ((VariableMappingExpressionEvaluatorWrapper)wrapped).wrapped : wrapped; + + // Instead of just saving the reference of the mapping, save the actual (trimmed) state of the mapping as it + // may change during copying (especially during flattening). A LinkedHashMap is used to retain ordering of + // original parameter names iterator. + this.variableMapping = new LinkedHashMap<>(); + + // Index map by variable names + Map names = new HashMap<>(); + for (PVariable originalVar : variableMapping.keySet()) { + names.put(originalVar.getName(), originalVar); + } + + // In case of rewrapping, current names are contained by the previous mapping + Map previousMapping = null; + if (rewrap){ + previousMapping = ((VariableMappingExpressionEvaluatorWrapper)wrapped).variableMapping; + } + + // Populate mapping + for (String inputParameterName : this.wrapped.getInputParameterNames()) { + String parameterName = rewrap ? previousMapping.get(inputParameterName) : inputParameterName; + Preconditions.checkArgument(parameterName != null); + PVariable original = names.get(parameterName); + Preconditions.checkArgument(original != null); + PVariable mapped = variableMapping.get(original); + if (mapped != null){ + this.variableMapping.put(inputParameterName, mapped.getName()); + } + } + } + + @Override + public String getShortDescription() { + return wrapped.getShortDescription(); + } + + @Override + public Iterable getInputParameterNames() { + return variableMapping.values(); + } + + @Override + public Object evaluateExpression(final IValueProvider provider) throws Exception { + return wrapped.evaluateExpression(variableName -> { + String mappedVariableName = variableMapping.get(variableName); + Preconditions.checkArgument(mappedVariableName != null, "Could not find variable %s", variableName); + return provider.getValue(mappedVariableName); + }); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/IStorageBackend.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/IStorageBackend.java new file mode 100644 index 00000000..16f40358 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/IStorageBackend.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.scopes; + +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.scopes.tables.ITableContext; +import tools.refinery.viatra.runtime.matchers.scopes.tables.ITableWriterBinary; +import tools.refinery.viatra.runtime.matchers.scopes.tables.ITableWriterUnary; + +/** + * An abstract storage backend that instantiates tables and coordinates transactions. + * + *

    EXPERIMENTAL. This class or interface has been added as + * part of a work in progress. There is no guarantee that this API will + * work or that it will remain the same. + * + * @author Gabor Bergmann + * + * @since 2.1 + */ +public interface IStorageBackend { + + + /** + * Marks the beginning of a transaction. + * In transaction mode, table updates may be temporarily delayed ({@link tools.refinery.viatra.runtime.matchers.scopes.tables.IIndexTable} methods may return stale answers) for better performance. + */ + void startTransaction(); + /** + * Marks the end of a transaction. + * Any updates delayed during the transaction must now be flushed. + */ + void finishTransaction(); + + /** + * Creates an index table for a simple value set. + * @param unique client promises to only insert a given tuple with multiplicity one + */ + ITableWriterUnary.Table createUnaryTable(IInputKey key, ITableContext tableContext, boolean unique); + /** + * Creates an index table for a simple source-target bidirectional mapping. + * @param unique client promises to only insert a given tuple with multiplicity one + */ + ITableWriterBinary.Table createBinaryTable(IInputKey key, ITableContext tableContext, boolean unique); + + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/SimpleLocalStorageBackend.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/SimpleLocalStorageBackend.java new file mode 100644 index 00000000..fd1f7b7e --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/SimpleLocalStorageBackend.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.scopes; + +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.scopes.tables.ITableContext; +import tools.refinery.viatra.runtime.matchers.scopes.tables.SimpleBinaryTable; +import tools.refinery.viatra.runtime.matchers.scopes.tables.SimpleUnaryTable; + +/** + * Basic storage backend implementation based on local collections. + * + *

    EXPERIMENTAL. This class or interface has been added as + * part of a work in progress. There is no guarantee that this API will + * work or that it will remain the same. + * + * @author Gabor Bergmann + * @since 2.1 + */ +public class SimpleLocalStorageBackend implements IStorageBackend { + + @Override + public void startTransaction() { + // NOP + } + + @Override + public void finishTransaction() { + // NOP + } + + @Override + public SimpleUnaryTable createUnaryTable(IInputKey key, ITableContext tableContext, boolean unique) { + return new SimpleUnaryTable<>(key, tableContext, unique); + } + + @Override + public SimpleBinaryTable createBinaryTable(IInputKey key, ITableContext tableContext, + boolean unique) { + return new SimpleBinaryTable<>(key, tableContext, unique); + } + + + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/SimpleRuntimeContext.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/SimpleRuntimeContext.java new file mode 100644 index 00000000..a3a827dc --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/SimpleRuntimeContext.java @@ -0,0 +1,132 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.scopes; + +import java.lang.reflect.InvocationTargetException; +import java.util.concurrent.Callable; + +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.context.IndexingService; +import tools.refinery.viatra.runtime.matchers.context.common.JavaTransitiveInstancesKey; +import tools.refinery.viatra.runtime.matchers.scopes.tables.IIndexTable; +import tools.refinery.viatra.runtime.matchers.tuple.ITuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; + +/** + * A simple demo implementation of the IQRC interface using tables. + * + *

    + * Usage: first, instantiate {@link IIndexTable} tables with this as the 'tableContext' argument, and call + * {@link #registerIndexTable(IIndexTable)} manually to register them. Afterwards, they will be visible to the query + * backends. + *

    + * EXPERIMENTAL. This class or interface has been added as + * part of a work in progress. There is no guarantee that this API will + * work or that it will remain the same. + * + * @author Gabor Bergmann + * @since 2.0 + */ +public class SimpleRuntimeContext extends TabularRuntimeContext { + + private IQueryMetaContext metaContext; + + public SimpleRuntimeContext(IQueryMetaContext metaContext) { + this.metaContext = metaContext; + } + + @Override + public void logError(String message) { + System.err.println(message); + } + + @Override + public IQueryMetaContext getMetaContext() { + return metaContext; + } + + @Override + public V coalesceTraversals(Callable callable) throws InvocationTargetException { + try { + return callable.call(); + } catch (Exception e) { + throw new InvocationTargetException(e); + } + } + + @Override + public boolean isCoalescing() { + return false; + } + + @Override + public boolean isIndexed(IInputKey key, IndexingService service) { + return peekIndexTable(key) != null; + } + + @Override + public void ensureIndexed(IInputKey key, IndexingService service) { + if (peekIndexTable(key) == null) + throw new IllegalArgumentException(key.getPrettyPrintableName()); + } + + @Override + public Object wrapElement(Object externalElement) { + return externalElement; + } + + @Override + public Object unwrapElement(Object internalElement) { + return internalElement; + } + + @Override + public Tuple wrapTuple(Tuple externalElements) { + return externalElements; + } + + @Override + public Tuple unwrapTuple(Tuple internalElements) { + return internalElements; + } + + @Override + public void ensureWildcardIndexing(IndexingService service) { + throw new UnsupportedOperationException(); + } + + @Override + public void executeAfterTraversal(Runnable runnable) throws InvocationTargetException { + runnable.run(); + } + + @Override + protected boolean isContainedInStatelessKey(IInputKey key, ITuple seed) { + if (key instanceof JavaTransitiveInstancesKey) { + Class instanceClass = forceGetWrapperInstanceClass((JavaTransitiveInstancesKey) key); + return instanceClass != null && instanceClass.isInstance(seed.get(0)); + } else + throw new IllegalArgumentException(key.getPrettyPrintableName()); + } + + private Class forceGetWrapperInstanceClass(JavaTransitiveInstancesKey key) { + Class instanceClass; + try { + instanceClass = key.forceGetWrapperInstanceClass(); + } catch (ClassNotFoundException e) { + logError( + "Could not load instance class for type constraint " + key.getWrappedKey() + ": " + e.getMessage()); + instanceClass = null; + } + return instanceClass; + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/TabularRuntimeContext.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/TabularRuntimeContext.java new file mode 100644 index 00000000..e99e24d3 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/TabularRuntimeContext.java @@ -0,0 +1,119 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.scopes; + +import java.util.Map; +import java.util.Optional; + +import tools.refinery.viatra.runtime.matchers.context.AbstractQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContextListener; +import tools.refinery.viatra.runtime.matchers.scopes.tables.IIndexTable; +import tools.refinery.viatra.runtime.matchers.scopes.tables.ITableContext; +import tools.refinery.viatra.runtime.matchers.tuple.ITuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.Accuracy; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; + +/** + * An abstract runtime context that serves enumerable input key instances from tables. + * + *

    + * Usage: first, instantiate {@link IIndexTable} tables with this as the 'tableContext' argument. Call + * {@link #registerIndexTable(IIndexTable)} to register them; this may happen either during a coalesced indexing, or on + * external initiation. Afterwards, they will be visible to the query backends. + *

    + * EXPERIMENTAL. This class or interface has been added as + * part of a work in progress. There is no guarantee that this API will + * work or that it will remain the same. + * + * @author Gabor Bergmann + * @since 2.0 + */ +public abstract class TabularRuntimeContext extends AbstractQueryRuntimeContext implements ITableContext { + + private Map instanceTables = CollectionsFactory.createMap(); + + public void registerIndexTable(IIndexTable table) { + IInputKey inputKey = table.getInputKey(); + instanceTables.put(inputKey, table); + } + + /** + * @return null if the table is not registered + */ + public IIndexTable peekIndexTable(IInputKey key) { + return instanceTables.get(key); + } + + /** + * If the table is not registered, {@link #handleUnregisteredTableRequest(IInputKey)} is invoked; it may handle it + * by raising an error or e.g. on-demand index construction + */ + public IIndexTable getIndexTable(IInputKey key) { + IIndexTable table = instanceTables.get(key); + if (table != null) + return table; + else + return handleUnregisteredTableRequest(key); + } + + /** + * Override this to provide on-demand table registration + */ + protected IIndexTable handleUnregisteredTableRequest(IInputKey key) { + throw new IllegalArgumentException(key.getPrettyPrintableName()); + } + + @Override + public int countTuples(IInputKey key, TupleMask seedMask, ITuple seed) { + return getIndexTable(key).countTuples(seedMask, seed); + } + + @Override + public Optional estimateCardinality(IInputKey key, TupleMask groupMask, Accuracy requiredAccuracy) { + return getIndexTable(key).estimateProjectionSize(groupMask, requiredAccuracy); + } + + @Override + public Iterable enumerateTuples(IInputKey key, TupleMask seedMask, ITuple seed) { + return getIndexTable(key).enumerateTuples(seedMask, seed); + } + + @Override + public Iterable enumerateValues(IInputKey key, TupleMask seedMask, ITuple seed) { + return getIndexTable(key).enumerateValues(seedMask, seed); + } + + @Override + public boolean containsTuple(IInputKey key, ITuple seed) { + if (key.isEnumerable()) { + return getIndexTable(key).containsTuple(seed); + } else { + return isContainedInStatelessKey(key, seed); + } + } + + @Override + public void addUpdateListener(IInputKey key, Tuple seed, IQueryRuntimeContextListener listener) { + getIndexTable(key).addUpdateListener(seed, listener); + } + @Override + public void removeUpdateListener(IInputKey key, Tuple seed, IQueryRuntimeContextListener listener) { + getIndexTable(key).removeUpdateListener(seed, listener); + } + + /** + * Handles non-enumerable input keys that are not backed by a table + */ + protected abstract boolean isContainedInStatelessKey(IInputKey key, ITuple seed); + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/AbstractIndexTable.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/AbstractIndexTable.java new file mode 100644 index 00000000..7b557c33 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/AbstractIndexTable.java @@ -0,0 +1,266 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.scopes.tables; + +import java.util.List; + +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContextListener; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType; +import tools.refinery.viatra.runtime.matchers.util.IMultiLookup; + +/** + *

    + * EXPERIMENTAL. This class or interface has been added as + * part of a work in progress. There is no guarantee that this API will + * work or that it will remain the same. + * + * @since 2.0 + * @author Gabor Bergmann + */ +public abstract class AbstractIndexTable implements IIndexTable { + + private IInputKey inputKey; + protected ITableContext tableContext; + + protected final TupleMask emptyMask; + protected final Tuple emptyTuple; + + + public AbstractIndexTable(IInputKey inputKey, ITableContext tableContext) { + this.inputKey = inputKey; + this.tableContext = tableContext; + + this.emptyMask = TupleMask.empty(getInputKey().getArity()); + this.emptyTuple = Tuples.flatTupleOf(new Object[inputKey.getArity()]); + } + + @Override + public String toString() { + return this.getClass().getSimpleName() + ":" + inputKey.getPrettyPrintableName(); + } + + @Override + public IInputKey getInputKey() { + return inputKey; + } + + protected void logError(String message) { + tableContext.logError(message); + } + + + /// UPDATE HANDLING SECTION + + // The entire mechanism is designed to accommodate a large number of update listeners, + // but maybe there will typically be only a single, universal (unseeded) listener? + // TODO Create special handling for that case. + + // Invariant: true iff #listenerGroupsAndSeed is nonempty + protected boolean emitNotifications = false; + // Subscribed listeners grouped by their seed mask (e.g. all those that seed columns 2 and 5 are together), + // groups are stored in a list for quick delivery-time iteration (at the expense of adding / removing); + // individual listeners can be looked up based on their seed tuple + protected List listenerGroups = CollectionsFactory.createObserverList(); + + + /** + * Implementors shall call this to deliver all notifications. + * Call may be conditioned to {@link #emitNotifications} + */ + protected void deliverChangeNotifications(Tuple updateTuple, boolean isInsertion) { + for (IListenersWithSameMask listenersForSeed : listenerGroups) { + listenersForSeed.deliver(updateTuple, isInsertion); + } + } + + @Override + public void addUpdateListener(Tuple seed, IQueryRuntimeContextListener listener) { + TupleMask seedMask; + if (seed == null) { + seed = emptyTuple; + seedMask = emptyMask; + } else { + seedMask = TupleMask.fromNonNullIndices(seed); + } + IListenersWithSameMask listenerGroup = getListenerGroup(seedMask); + if (listenerGroup == null) { // create new group + switch (seedMask.getSize()) { + case 0: + listenerGroup = new UniversalListeners(); + break; + case 1: + listenerGroup = new ColumnBoundListeners(seedMask.indices[0]); + break; + default: + listenerGroup = new GenericBoundListeners(seedMask); + } + listenerGroups.add(listenerGroup); + emitNotifications = true; + } + listenerGroup.addUpdateListener(seed, listener); + } + + @Override + public void removeUpdateListener(Tuple seed, IQueryRuntimeContextListener listener) { + TupleMask seedMask; + if (seed == null) { + seed = emptyTuple; + seedMask = emptyMask; + } else { + seedMask = TupleMask.fromNonNullIndices(seed); + } + IListenersWithSameMask listenerGroup = getListenerGroup(seedMask); + if (listenerGroup == null) + throw new IllegalStateException("no listener subscribed with mask" + seedMask); + + if (listenerGroup.removeUpdateListener(seed, listener)) { + listenerGroups.remove(listenerGroup); + emitNotifications = !listenerGroups.isEmpty(); + } + } + + protected IListenersWithSameMask getListenerGroup(TupleMask seedMask) { + for (IListenersWithSameMask candidateGroup : listenerGroups) { // group already exists? + if (seedMask.equals(candidateGroup.getSeedMask())) { + return candidateGroup; + } + } + return null; + } + + + /** + * Represents all listeners subscribed to seeds with the given seed mask. + * + * @author Bergmann Gabor + */ + protected static interface IListenersWithSameMask { + + public TupleMask getSeedMask(); + + public void deliver(Tuple updateTuple, boolean isInsertion); + + public void addUpdateListener(Tuple originalSeed, IQueryRuntimeContextListener listener); + /** + * @return true if this was the last listener, and the {@link IListenersWithSameMask} can be disposed of. + */ + public boolean removeUpdateListener(Tuple originalSeed, IQueryRuntimeContextListener listener); + } + /** + * Listeners interested in all tuples + */ + protected final class UniversalListeners implements IListenersWithSameMask { + private final TupleMask mask = TupleMask.empty(inputKey.getArity()); + private List listeners = CollectionsFactory.createObserverList(); + + @Override + public TupleMask getSeedMask() { + return mask; + } + @Override + public void deliver(Tuple updateTuple, boolean isInsertion) { + IInputKey key = inputKey; + for (IQueryRuntimeContextListener listener : listeners) { + listener.update(key, updateTuple, isInsertion); + } + } + @Override + public void addUpdateListener(Tuple originalSeed, IQueryRuntimeContextListener listener) { + listeners.add(listener); + } + @Override + public boolean removeUpdateListener(Tuple originalSeed, IQueryRuntimeContextListener listener) { + listeners.remove(listener); + return listeners.isEmpty(); + } + } + /** + * Listeners interested in all tuples seeded by a single columns + */ + protected final class ColumnBoundListeners implements IListenersWithSameMask { + private int seedPosition; + protected final TupleMask mask; + // indexed by projected seed tuple + protected IMultiLookup listeners = + CollectionsFactory.createMultiLookup(Object.class, MemoryType.SETS, Object.class); + + public ColumnBoundListeners(int seedPosition) { + this.seedPosition = seedPosition; + this.mask = TupleMask.selectSingle(seedPosition, inputKey.getArity()); + } + + @Override + public TupleMask getSeedMask() { + return mask; + } + @Override + public void deliver(Tuple updateTuple, boolean isInsertion) { + IInputKey key = inputKey; + Object projectedSeed = updateTuple.get(seedPosition); + for (IQueryRuntimeContextListener listener : listeners.lookupOrEmpty(projectedSeed)) { + listener.update(key, updateTuple, isInsertion); + } + } + @Override + public void addUpdateListener(Tuple originalSeed, IQueryRuntimeContextListener listener) { + Object projectedSeed = originalSeed.get(seedPosition); + listeners.addPair(projectedSeed, listener); + } + @Override + public boolean removeUpdateListener(Tuple originalSeed, IQueryRuntimeContextListener listener) { + Object projectedSeed = originalSeed.get(seedPosition); + listeners.removePair(projectedSeed, listener); + return listeners.countKeys() == 0; + } + } + /** + * Listeners interested in all tuples seeded by a tuple of values + */ + protected final class GenericBoundListeners implements IListenersWithSameMask { + protected final TupleMask mask; + // indexed by projected seed tuple + protected IMultiLookup listeners = + CollectionsFactory.createMultiLookup(Object.class, MemoryType.SETS, Object.class); + + public GenericBoundListeners(TupleMask mask) { + this.mask = mask; + } + + @Override + public TupleMask getSeedMask() { + return mask; + } + @Override + public void deliver(Tuple updateTuple, boolean isInsertion) { + IInputKey key = inputKey; + Tuple projectedSeed = mask.transform(updateTuple); + for (IQueryRuntimeContextListener listener : listeners.lookupOrEmpty(projectedSeed)) { + listener.update(key, updateTuple, isInsertion); + } + } + @Override + public void addUpdateListener(Tuple originalSeed, IQueryRuntimeContextListener listener) { + Tuple projectedSeed = mask.transform(originalSeed); + listeners.addPair(projectedSeed, listener); + } + @Override + public boolean removeUpdateListener(Tuple originalSeed, IQueryRuntimeContextListener listener) { + Tuple projectedSeed = mask.transform(originalSeed); + listeners.removePair(projectedSeed, listener); + return listeners.countKeys() == 0; + } + } + + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/DefaultIndexTable.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/DefaultIndexTable.java new file mode 100644 index 00000000..39475d11 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/DefaultIndexTable.java @@ -0,0 +1,143 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.scopes.tables; + +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; + +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.memories.MaskedTupleMemory; +import tools.refinery.viatra.runtime.matchers.tuple.ITuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.Accuracy; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.IMemory; + +/** + * Demo default implementation. + *

    + * EXPERIMENTAL. This class or interface has been added as + * part of a work in progress. There is no guarantee that this API will + * work or that it will remain the same. + * + * @since 2.0 + * @author Gabor Bergmann + */ +public class DefaultIndexTable extends AbstractIndexTable implements ITableWriterGeneric { + + protected IMemory rows = CollectionsFactory.createMultiset(); // TODO use SetMemory if unique + protected Map> indexMemories = CollectionsFactory.createMap(); + private boolean unique; + + /** + * @param unique + * client promises to only insert a given tuple with multiplicity one + */ + public DefaultIndexTable(IInputKey inputKey, ITableContext tableContext, boolean unique) { + super(inputKey, tableContext); + this.unique = unique; + } + + @Override + public void write(Direction direction, Tuple row) { + if (direction == Direction.INSERT) { + boolean changed = rows.addOne(row); + if (unique && !changed) { + String msg = String.format( + "Error: trying to add duplicate row %s to the unique table %s. This indicates some errors in underlying model representation.", + row, getInputKey().getPrettyPrintableName()); + logError(msg); + } + if (changed) { + for (MaskedTupleMemory indexMemory : indexMemories.values()) { + indexMemory.add(row); + } + if (emitNotifications) { + deliverChangeNotifications(row, true); + } + } + } else { // DELETE + boolean changed = rows.removeOne(row); + if (unique && !changed) { + String msg = String.format( + "Error: trying to remove duplicate value %s from the unique table %s. This indicates some errors in underlying model representation.", + row, getInputKey().getPrettyPrintableName()); + logError(msg); + } + if (changed) { + for (MaskedTupleMemory indexMemory : indexMemories.values()) { + indexMemory.remove(row); + } + if (emitNotifications) { + deliverChangeNotifications(row, false); + } + } + } + } + + @Override + public boolean containsTuple(ITuple seed) { + return rows.distinctValues().contains(seed); + } + + private MaskedTupleMemory getIndexMemory(TupleMask seedMask) { + return indexMemories.computeIfAbsent(seedMask, mask -> { + MaskedTupleMemory memory = MaskedTupleMemory.create(seedMask, MemoryType.SETS, DefaultIndexTable.this); + for (Tuple tuple : rows.distinctValues()) { + memory.add(tuple); + } + return memory; + }); + } + + @Override + public int countTuples(TupleMask seedMask, ITuple seed) { + switch (seedMask.getSize()) { + case 0: // unseeded + return rows.size(); + default: + return getIndexMemory(seedMask).getOrEmpty(seed).size(); + } + } + + @Override + public Optional estimateProjectionSize(TupleMask groupMask, Accuracy requiredAccuracy) { + // always exact count + if (groupMask.getSize() == 0) { + return rows.size() == 0 ? Optional.of(0L) : Optional.of(1L); + } else if (groupMask.getSize() == this.emptyTuple.getSize()) { + return Optional.of((long) rows.size()); + } else { + return Optional.of((long)getIndexMemory(groupMask).getKeysetSize()); + } + } + + @Override + public Iterable enumerateTuples(TupleMask seedMask, ITuple seed) { + return getIndexMemory(seedMask).getOrEmpty(seed); + } + + @Override + public Stream streamTuples(TupleMask seedMask, ITuple seed) { + return getIndexMemory(seedMask).getOrEmpty(seed).stream(); + } + + @Override + public Stream streamValues(TupleMask seedMask, ITuple seed) { + // we assume there is a single omitted index in the mask + int queriedColumn = seedMask.getFirstOmittedIndex().getAsInt(); + return getIndexMemory(seedMask).getOrEmpty(seed).stream() + .map(row2 -> row2.get(queriedColumn)); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/DisjointUnionTable.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/DisjointUnionTable.java new file mode 100644 index 00000000..a3a65f14 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/DisjointUnionTable.java @@ -0,0 +1,192 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.scopes.tables; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Stream; + +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContextListener; +import tools.refinery.viatra.runtime.matchers.tuple.ITuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.Accuracy; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; + +/** + * Disjoint union of the provided child tables. + * + * Used e.g. to present a transitive instance table as a view composed from direct instance tables. + *

    + * EXPERIMENTAL. This class or interface has been added as + * part of a work in progress. There is no guarantee that this API will + * work or that it will remain the same. + * + * @since 2.0 + * @author Gabor Bergmann + */ +public class DisjointUnionTable extends AbstractIndexTable { + + protected List childTables = CollectionsFactory.createObserverList(); + + public DisjointUnionTable(IInputKey inputKey, ITableContext tableContext) { + super(inputKey, tableContext); + } + + public List getChildTables() { + return Collections.unmodifiableList(childTables); + } + + /** + * Precondition: the new child currently is, and will forever stay, disjoint from any other child tables. + */ + public void addChildTable(IIndexTable child) { + if (getInputKey().getArity() != child.getInputKey().getArity()) + throw new IllegalArgumentException(child.toString()); + + childTables.add(child); + + if (emitNotifications) { + for (Tuple tuple : child.enumerateTuples(emptyMask, Tuples.staticArityFlatTupleOf())) { + deliverChangeNotifications(tuple, true); + } + } + } + + @Override + public int countTuples(TupleMask seedMask, ITuple seed) { + int count = 0; + for (IIndexTable child : childTables) { + count += child.countTuples(seedMask, seed); + } + return count; + } + + + @Override + public Optional estimateProjectionSize(TupleMask groupMask, Accuracy requiredAccuracy) { + // exact results for trivial projections + if (groupMask.getSize() == 0) { + for (IIndexTable child : childTables) { + if (0 != child.countTuples(this.emptyMask, Tuples.staticArityFlatTupleOf())) + return Optional.of(1L); + } + return Optional.of(0L); + } else if (groupMask.getSize() == emptyTuple.getSize()) { + return Optional.of((long)countTuples(this.emptyMask, Tuples.staticArityFlatTupleOf())); + } + // summing child tables is an upper bound + if (Accuracy.BEST_UPPER_BOUND.atLeastAsPreciseAs(requiredAccuracy)) { + return Optional.of((long)countTuples(this.emptyMask, Tuples.staticArityFlatTupleOf())); + } else { // (Accuracy.BEST_LOWER_BOUND == requiredAccuracy) + //projections of child tables may coincide, but the largest one is still a lower bound + Optional maxProjection = Optional.empty(); + for (IIndexTable child : childTables) { + Optional estimateOfChild = child.estimateProjectionSize(groupMask, requiredAccuracy); + if (estimateOfChild.isPresent()) { + maxProjection = Optional.of(Math.max(estimateOfChild.get(), maxProjection.orElse(0L))); + } + } + return maxProjection; + } + } + + @Override + public Stream streamTuples(TupleMask seedMask, ITuple seed) { + Stream stream = Stream.empty(); + for (IIndexTable child : childTables) { + Stream childStream = child.streamTuples(seedMask, seed); + stream = Stream.concat(stream, childStream); + } + return stream; + } + + @Override + public Stream streamValues(TupleMask seedMask, ITuple seed) { + Stream stream = Stream.empty(); + for (IIndexTable child : childTables) { + Stream childStream = child.streamValues(seedMask, seed); + stream = Stream.concat(stream, childStream); + } + return stream; + } + + @Override + public boolean containsTuple(ITuple seed) { + for (IIndexTable child : childTables) { + if (child.containsTuple(seed)) + return true; + } + return false; + } + + @Override + public void addUpdateListener(Tuple seed, IQueryRuntimeContextListener listener) { + super.addUpdateListener(seed, listener); + + for (IIndexTable table : childTables) { + table.addUpdateListener(seed, new ListenerWrapper(listener)); + } + } + @Override + public void removeUpdateListener(Tuple seed, IQueryRuntimeContextListener listener) { + super.removeUpdateListener(seed, listener); + + for (IIndexTable table : childTables) { + table.removeUpdateListener(seed, new ListenerWrapper(listener)); + } + } + + + // TODO this would not be necessary + // if we moved from IQRCL to an interface that does not expose the input key + private class ListenerWrapper implements IQueryRuntimeContextListener { + + private IQueryRuntimeContextListener wrappedListener; + public ListenerWrapper(IQueryRuntimeContextListener wrappedListener) { + this.wrappedListener = wrappedListener; + } + + @Override + public void update(IInputKey key, Tuple updateTuple, boolean isInsertion) { + wrappedListener.update(getInputKey(), updateTuple, isInsertion); + } + + @Override + public int hashCode() { + return Objects.hash(wrappedListener, DisjointUnionTable.this); + } + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ListenerWrapper other = (ListenerWrapper) obj; + if (!getOuterType().equals(other.getOuterType())) + return false; + return Objects.equals(wrappedListener, other.wrappedListener); + } + private DisjointUnionTable getOuterType() { + return DisjointUnionTable.this; + } + @Override + public String toString() { + return "Wrapper to DisjointUnion("+getInputKey().getPrettyPrintableName()+") for " + wrappedListener; + } + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/IIndexTable.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/IIndexTable.java new file mode 100644 index 00000000..be375393 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/IIndexTable.java @@ -0,0 +1,212 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.scopes.tables; + +import java.util.Iterator; +import java.util.Optional; +import java.util.stream.Stream; + +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContextListener; +import tools.refinery.viatra.runtime.matchers.tuple.ITuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.Accuracy; + +/** + * Read-only interface that provides the {@link IInputKey}-specific slice of an instance store to realize a + * {@link IQueryRuntimeContext}. Implemented by a customizable data store that is responsible for: + *

      + *
    • storing the instance tuples of the {@link IInputKey},
    • + *
    • providing efficient lookup via storage-specific indexing,
    • + *
    • delivering notifications. (TODO not designed yet)
    • + *
    + * + *

    + * Can be specialized for unary / binary / etc., opposite edges or node subtypes, specific types, distributed storage, + * etc. + *

    + * Writeable API is specific to the customized implementations (e.g. unary). + * + *

    + * Precondition: the associated input key is enumerable, see {@link IQueryMetaContext#isEnumerable(IInputKey)}. + *

    + * EXPERIMENTAL. This class or interface has been added as + * part of a work in progress. There is no guarantee that this API will + * work or that it will remain the same. + * + * @since 2.0 + * @author Gabor Bergmann + * @noimplement This interface is not intended to be implemented directly. Extend {@link AbstractIndexTable} instead. + */ +public interface IIndexTable { + + // TODO add superinterface that represents a statistics-only counter? + + /** + * @return the input key indexed by this table + */ + public IInputKey getInputKey(); + + /** + * Returns the tuples, optionally seeded with the given tuple. + * + *

    Consider using the more idiomatic {@link #streamTuples(TupleMask, ITuple)} instead. + * + * @param seedMask + * a mask that extracts those parameters of the input key (from the entire parameter list) that should be + * bound to a fixed value; must not be null. Note: any given index must occur at most + * once in seedMask. + * @param seed + * the tuple of fixed values restricting the row set to be considered, in the same order as given in + * parameterSeedMask, so that for each considered row tuple, + * projectedParameterSeed.equals(parameterSeedMask.transform(row)) should hold. Must not be null. + * @return the tuples in the table for the given key and seed + */ + @SuppressWarnings("unchecked") + public default Iterable enumerateTuples(TupleMask seedMask, ITuple seed) { + return () -> (Iterator) (streamTuples(seedMask, seed).iterator()); + } + + /** + * Returns the tuples, optionally seeded with the given tuple. + * + * @param seedMask + * a mask that extracts those parameters of the input key (from the entire parameter list) that should be + * bound to a fixed value; must not be null. Note: any given index must occur at most + * once in seedMask. + * @param seed + * the tuple of fixed values restricting the row set to be considered, in the same order as given in + * parameterSeedMask, so that for each considered row tuple, + * projectedParameterSeed.equals(parameterSeedMask.transform(row)) should hold. Must not be null. + * @return the tuples in the table for the given key and seed + * @since 2.1 + */ + public Stream streamTuples(TupleMask seedMask, ITuple seed); + + /** + * Simpler form of {@link #enumerateTuples(TupleMask, ITuple)} in the case where all values of the tuples are bound + * by the seed except for one. + * + *

    + * Selects the tuples in the table, optionally seeded with the given tuple, and then returns the single value from + * each tuple which is not bound by the seed mask. + * + *

    Consider using the more idiomatic {@link #streamValues(TupleMask, ITuple)} instead. + * + * @param seedMask + * a mask that extracts those parameters of the input key (from the entire parameter list) that should be + * bound to a fixed value; must not be null. Note: any given index must occur at most + * once in seedMask, and seedMask must include all parameters in any arbitrary order except one. + * @param seed + * the tuple of fixed values restricting the row set to be considered, in the same order as given in + * parameterSeedMask, so that for each considered row tuple, + * projectedParameterSeed.equals(parameterSeedMask.transform(row)) should hold. Must not be null. + * @return the objects in the table for the given key and seed + * + */ + @SuppressWarnings("unchecked") + public default Iterable enumerateValues(TupleMask seedMask, ITuple seed) { + return () -> (Iterator) (streamValues(seedMask, seed).iterator()); + } + + /** + * Simpler form of {@link #enumerateTuples(TupleMask, ITuple)} in the case where all values of the tuples are bound + * by the seed except for one. + * + *

    + * Selects the tuples in the table, optionally seeded with the given tuple, and then returns the single value from + * each tuple which is not bound by the seed mask. + * + * @param seedMask + * a mask that extracts those parameters of the input key (from the entire parameter list) that should be + * bound to a fixed value; must not be null. Note: any given index must occur at most + * once in seedMask, and seedMask must include all parameters in any arbitrary order except one. + * @param seed + * the tuple of fixed values restricting the row set to be considered, in the same order as given in + * parameterSeedMask, so that for each considered row tuple, + * projectedParameterSeed.equals(parameterSeedMask.transform(row)) should hold. Must not be null. + * @return the objects in the table for the given key and seed + * + * @since 2.1 + */ + public Stream streamValues(TupleMask seedMask, ITuple seed); + + /** + * Simpler form of {@link #enumerateTuples(TupleMask, ITuple)} in the case where all values of the tuples are bound + * by the seed. + * + *

    + * Returns whether the given tuple is in the table identified by the input key. + * + * @param seed + * a row tuple of fixed values whose presence in the table is queried + * @return true iff there is a row tuple contained in the table that corresponds to the given seed + */ + public boolean containsTuple(ITuple seed); + + /** + * Returns the number of tuples, optionally seeded with the given tuple. + * + *

    + * Selects the tuples in the table, optionally seeded with the given tuple, and then returns their number. + * + * @param seedMask + * a mask that extracts those parameters of the input key (from the entire parameter list) that should be + * bound to a fixed value; must not be null. Note: any given index must occur at most + * once in seedMask. + * @param seed + * the tuple of fixed values restricting the row set to be considered, in the same order as given in + * parameterSeedMask, so that for each considered row tuple, + * projectedParameterSeed.equals(parameterSeedMask.transform(row)) should hold. Must not be null. + * @return the number of tuples in the table for the given key and seed + * + */ + public int countTuples(TupleMask seedMask, ITuple seed); + + /** + * Gives an estimate of the number of different groups the tuples of the table are projected into by the given mask + * (e.g. for an identity mask, this means the full relation size). The estimate must meet the required accuracy. + * + *

    Derived tables may return {@link Optional#empty()} if it would be costly to provide an answer up to the required precision. + * Direct storage tables are expected to always be able to give an exact count. + * + *

    PRE: {@link TupleMask#isNonrepeating()} must hold for the group mask. + * + * @since 2.1 + */ + public Optional estimateProjectionSize(TupleMask groupMask, Accuracy requiredAccuracy); + + /** + * Subscribes for updates in the table, optionally seeded with the given tuple. + *

    This should be called after initializing a result cache by an enumeration method. + * + * @param seed can be null or a tuple with matching arity; + * if non-null, notifications will delivered only about those updates of the table + * that match the seed at positions where the seed is non-null. + * @param listener will be notified of future changes + * + * @since 2.1 + */ + public void addUpdateListener(Tuple seed, IQueryRuntimeContextListener listener); + + /** + * Unsubscribes from updates in the table, optionally seeded with the given tuple. + * + * @param seed can be null or a tuple with matching arity; + * see {@link #addUpdateListener(Tuple, IQueryRuntimeContextListener)} for definition. + * @param listener will no longer be notified of future changes + * + * @since 2.1 + */ + public void removeUpdateListener(Tuple seed, IQueryRuntimeContextListener listener); + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/ITableContext.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/ITableContext.java new file mode 100644 index 00000000..69e83cd7 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/ITableContext.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.scopes.tables; + +/** + * Callbacks that {@link IIndexTable} implementations are expected to invoke on their environment. + *

    + * EXPERIMENTAL. This class or interface has been added as + * part of a work in progress. There is no guarantee that this API will + * work or that it will remain the same. + * + * @since 2.0 + * @author Gabor Bergmann + */ +public interface ITableContext { + + // TODO notifications? + + /** + * Indicates that an error has occurred in maintaining an index table, e.g. duplicate value. + */ + public void logError(String message); +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/ITableWriterBinary.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/ITableWriterBinary.java new file mode 100644 index 00000000..fb614014 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/ITableWriterBinary.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.scopes.tables; + +import tools.refinery.viatra.runtime.matchers.util.Direction; + +/** + * Modifies the contents of a binary {@link IIndexTable}. + *

    + * EXPERIMENTAL. This class or interface has been added as + * part of a work in progress. There is no guarantee that this API will + * work or that it will remain the same. + * + * @since 2.0 + * @author Gabor Bergmann + */ +public interface ITableWriterBinary { + /** + * Adds/removes a row to/from the table. + * + * @param direction + * tells whether putting a row into the table or deleting + * + * TODO: store as multiset, return bool? + */ + void write(Direction direction, Source source, Target target); + + /** + * Intersection type for writers that are also tables + */ + interface Table extends ITableWriterBinary, IIndexTable { + } + + /** + * /dev/null implementation + * + * @author Gabor Bergmann + */ + static class Nop implements ITableWriterBinary { + @Override + public void write(Direction direction, Source source, Target target) { + // NO-OP + } + + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/ITableWriterGeneric.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/ITableWriterGeneric.java new file mode 100644 index 00000000..fb1ebcc0 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/ITableWriterGeneric.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.scopes.tables; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; + +/** + * Modifies the contents of an {@link IIndexTable}. + *

    + * EXPERIMENTAL. This class or interface has been added as + * part of a work in progress. There is no guarantee that this API will + * work or that it will remain the same. + * + * @since 2.0 + * @author Gabor Bergmann + */ +public interface ITableWriterGeneric { + + /** + * Adds/removes a row to/from the table. + * + * @param direction + * tells whether putting a row into the table or deleting TODO: store as multiset, return bool? + */ + void write(Direction direction, Tuple row); + + /** + * Intersection type for writers that are also tables + */ + interface Table extends ITableWriterGeneric, IIndexTable { + } + + /** + * /dev/null implementation + * + * @author Gabor Bergmann + */ + static class Nop implements ITableWriterGeneric { + @Override + public void write(Direction direction, Tuple row) { + // NO-OP + } + + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/ITableWriterUnary.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/ITableWriterUnary.java new file mode 100644 index 00000000..f90d5362 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/ITableWriterUnary.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.scopes.tables; + +import tools.refinery.viatra.runtime.matchers.util.Direction; + +/** + * Modifies the contents of a unary {@link IIndexTable}. + *

    + * EXPERIMENTAL. This class or interface has been added as + * part of a work in progress. There is no guarantee that this API will + * work or that it will remain the same. + * + * @since 2.0 + * @author Gabor Bergmann + * + */ +public interface ITableWriterUnary { + + /** + * Adds/removes a row to/from the table. + * + * @param direction + * tells whether putting a row into the table or deleting TODO: store as multiset, return bool? + */ + void write(Direction direction, Value value); + + /** + * Intersection type for writers that are also tables + */ + interface Table extends ITableWriterUnary, IIndexTable { + } + + /** + * /dev/null implementation + * + * @author Gabor Bergmann + */ + static class Nop implements ITableWriterUnary { + @Override + public void write(Direction direction, Value value) { + // NO-OP + } + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/SimpleBinaryTable.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/SimpleBinaryTable.java new file mode 100644 index 00000000..588ed9d2 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/SimpleBinaryTable.java @@ -0,0 +1,320 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.scopes.tables; + +import java.util.Collections; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Stream; + +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.tuple.ITuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.Accuracy; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.IMemoryView; +import tools.refinery.viatra.runtime.matchers.util.IMultiLookup; +import tools.refinery.viatra.runtime.matchers.util.IMultiLookup.ChangeGranularity; + +/** + * Simple source-target bidirectional mapping. + * + *

    + * TODO: specialize for to-one features and unique to-many features + *

    + * TODO: on-demand construction of valueToHolderMap + *

    + * TODO: support for lean indexing, opposites, long surrogate ids, etc. + *

    + * EXPERIMENTAL. This class or interface has been added as + * part of a work in progress. There is no guarantee that this API will + * work or that it will remain the same. + * + * @since 2.0 + * @author Gabor Bergmann + */ +public class SimpleBinaryTable extends AbstractIndexTable + implements ITableWriterBinary.Table { + + /** + * value -> holder(s) + *

    + * this is currently the primary store, may hold duplicates depending on the unique parameter + */ + private IMultiLookup valueToHolderMap; + /** + * holder -> value(s); constructed on-demand, null if unused + */ + private IMultiLookup holderToValueMap; + private int totalRowCount = 0; + private boolean unique; + + /** + * @param unique + * client promises to only insert a given tuple with multiplicity one + */ + public SimpleBinaryTable(IInputKey inputKey, ITableContext tableContext, boolean unique) { + super(inputKey, tableContext); + this.unique = unique; + valueToHolderMap = CollectionsFactory.createMultiLookup(Object.class, + unique ? MemoryType.SETS : MemoryType.MULTISETS, Object.class); + if (2 != inputKey.getArity()) + throw new IllegalArgumentException(inputKey.toString()); + } + + @Override + public void write(Direction direction, Source holder, Target value) { + if (direction == Direction.INSERT) { + try { + // TODO we currently assume V2H map exists + boolean changed = addToValueToHolderMap(value, holder); + if (holderToValueMap != null) { + addToHolderToValueMap(value, holder); + } + if (changed) { + totalRowCount++; + if (emitNotifications) { + deliverChangeNotifications(Tuples.staticArityFlatTupleOf(holder, value), true); + } + } + } catch (IllegalStateException ex) { // if unique table and duplicate tuple + String msg = String.format( + "Error: trying to add duplicate value %s to the unique feature %s of host object %s. This indicates some errors in underlying model representation.", + value, getInputKey().getPrettyPrintableName(), holder); + logError(msg); + } + } else { // DELETE + try { + // TODO we currently assume V2H map exists + boolean changed = removeFromValueToHolderMap(value, holder); + if (holderToValueMap != null) { + removeFromHolderToValueMap(value, holder); + } + if (changed) { + totalRowCount--; + if (emitNotifications) { + deliverChangeNotifications(Tuples.staticArityFlatTupleOf(holder, value), false); + } + } + } catch (IllegalStateException ex) { // if unique table and duplicate tuple + String msg = String.format( + "Error: trying to remove non-existing value %s from the feature %s of host object %s. This indicates some errors in underlying model representation.", + value, getInputKey().getPrettyPrintableName(), holder); + logError(msg); + } + } + } + + private boolean addToHolderToValueMap(Target value, Source holder) { + return ChangeGranularity.DUPLICATE != holderToValueMap.addPair(holder, value); + } + + private boolean addToValueToHolderMap(Target value, Source holder) { + return ChangeGranularity.DUPLICATE != valueToHolderMap.addPair(value, holder); + } + + /** + * @throws IllegalStateException + */ + private boolean removeFromHolderToValueMap(Target value, Source holder) { + return ChangeGranularity.DUPLICATE != holderToValueMap.removePair(holder, value); + } + + /** + * @throws IllegalStateException + */ + private boolean removeFromValueToHolderMap(Target value, Source holder) { + return ChangeGranularity.DUPLICATE != valueToHolderMap.removePair(value, holder); + } + + @SuppressWarnings("unchecked") + @Override + public int countTuples(TupleMask seedMask, ITuple seed) { + switch (seedMask.getSize()) { + case 0: // unseeded + // TODO we currently assume V2H map exists + return totalRowCount; + case 1: // lookup by source or target + int seedIndex = seedMask.indices[0]; + if (seedIndex == 0) { // lookup by source + Source source = (Source) seed.get(0); + return getDistinctValuesOfHolder(source).size(); + } else if (seedIndex == 1) { // lookup by target + Target target = (Target) seed.get(0); + return getDistinctHoldersOfValue(target).size(); + } else + throw new IllegalArgumentException(seedMask.toString()); + case 2: // containment check + // hack: if mask is not identity, then it is [1,0]/2, which is its own inverse + Source source = (Source) seedMask.getValue(seed, 0); + Target target = (Target) seedMask.getValue(seed, 1); + if (containsRow(source, target)) + return 1; + else + return 0; + default: + throw new IllegalArgumentException(seedMask.toString()); + } + } + + @Override + public Optional estimateProjectionSize(TupleMask groupMask, Accuracy requiredAccuracy) { + // always exact count + if (groupMask.getSize() == 0) { + return totalRowCount == 0 ? Optional.of(0L) : Optional.of(1L); + } else if (groupMask.getSize() == 2) { + return Optional.of((long)totalRowCount); + } else if (groupMask.indices[0] == 0) { // project to holder + return Optional.of((long)getHolderToValueMap().countKeys()); + } else { // (groupMask.indices[0] == 0) // project to value + return Optional.of((long)getValueToHolderMap().countKeys()); + } + } + + @Override + public Stream streamTuples(TupleMask seedMask, ITuple seed) { + switch (seedMask.getSize()) { + case 0: // unseeded + // TODO we currently assume V2H map exists + return getAllDistinctValuesStream() + .flatMap(value -> valueToHolderMap.lookup(value).distinctValues().stream() + .map(source -> Tuples.staticArityFlatTupleOf(source, value))); + + case 1: // lookup by source or target + int seedIndex = seedMask.indices[0]; + if (seedIndex == 0) { // lookup by source + @SuppressWarnings("unchecked") + Source source = (Source) seed.get(0); + return getDistinctValuesOfHolder(source).stream() + .map(target -> Tuples.staticArityFlatTupleOf(source, target)); + } else if (seedIndex == 1) { // lookup by target + @SuppressWarnings("unchecked") + Target target = (Target) seed.get(0); + return getDistinctHoldersOfValue(target).stream() + .map(source -> Tuples.staticArityFlatTupleOf(source, target)); + } else + throw new IllegalArgumentException(seedMask.toString()); + case 2: // containment check + // hack: if mask is not identity, then it is [1,0]/2, which is its own inverse + @SuppressWarnings("unchecked") + Source source = (Source) seedMask.getValue(seed, 0); + @SuppressWarnings("unchecked") + Target target = (Target) seedMask.getValue(seed, 1); + + if (containsRow(source, target)) + return Stream.of(Tuples.staticArityFlatTupleOf(source, target)); + else + return Stream.empty(); + default: + throw new IllegalArgumentException(seedMask.toString()); + } + } + + @Override + @SuppressWarnings("unchecked") + public Iterable enumerateValues(TupleMask seedMask, ITuple seed) { + if (seedMask.getSize() != 1) + throw new IllegalArgumentException(seedMask.toString()); + + int seedIndex = seedMask.indices[0]; + if (seedIndex == 0) { // lookup by source + return getDistinctValuesOfHolder((Source) seed.get(0)); + } else if (seedIndex == 1) { // lookup by target + return getDistinctHoldersOfValue((Target) seed.get(0)); + } else + throw new IllegalArgumentException(seedMask.toString()); + + } + @Override + public Stream streamValues(TupleMask seedMask, ITuple seed) { + if (seedMask.getSize() != 1) + throw new IllegalArgumentException(seedMask.toString()); + + int seedIndex = seedMask.indices[0]; + if (seedIndex == 0) { // lookup by source + return getDistinctValuesOfHolder((Source) seed.get(0)).stream(); + } else if (seedIndex == 1) { // lookup by target + return getDistinctHoldersOfValue((Target) seed.get(0)).stream(); + } else + throw new IllegalArgumentException(seedMask.toString()); + + } + + @Override + @SuppressWarnings("unchecked") + public boolean containsTuple(ITuple seed) { + return containsRow((Source) seed.get(0), (Target) seed.get(1)); + } + + public boolean containsRow(Source source, Target target) { + // TODO we currently assume V2H map exists + if (valueToHolderMap != null) { + IMemoryView holders = valueToHolderMap.lookup(target); + return holders != null && holders.containsNonZero(source); + } else + throw new UnsupportedOperationException("TODO implement"); + } + + public Iterable getAllDistinctHolders() { + return getHolderToValueMap().distinctKeys(); + } + public Stream getAllDistinctHoldersStream() { + return getHolderToValueMap().distinctKeysStream(); + } + public Iterable getAllDistinctValues() { + return getValueToHolderMap().distinctKeys(); + } + public Stream getAllDistinctValuesStream() { + return getValueToHolderMap().distinctKeysStream(); + } + + public Set getDistinctHoldersOfValue(Target value) { + IMemoryView holdersMultiset = getValueToHolderMap().lookup(value); + if (holdersMultiset == null) + return Collections.emptySet(); + else + return holdersMultiset.distinctValues(); + } + + public Set getDistinctValuesOfHolder(Source holder) { + IMemoryView valuesMultiset = getHolderToValueMap().lookup(holder); + if (valuesMultiset == null) + return Collections.emptySet(); + else + return valuesMultiset.distinctValues(); + } + + private IMultiLookup getHolderToValueMap() { + if (holderToValueMap == null) { + holderToValueMap = CollectionsFactory.createMultiLookup(Object.class, MemoryType.SETS, // no duplicates, as + // this is the + // secondary + // collection + Object.class); + + // TODO we currently assume V2H map exists + for (Target value : valueToHolderMap.distinctKeys()) { + for (Source holder : valueToHolderMap.lookup(value).distinctValues()) { + holderToValueMap.addPair(holder, value); + } + } + } + return holderToValueMap; + } + + private IMultiLookup getValueToHolderMap() { + // TODO we currently assume V2H map exists + return valueToHolderMap; + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/SimpleUnaryTable.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/SimpleUnaryTable.java new file mode 100644 index 00000000..428ea78b --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/scopes/tables/SimpleUnaryTable.java @@ -0,0 +1,140 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.scopes.tables; + +import java.util.Optional; +import java.util.stream.Stream; + +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.tuple.ITuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.Accuracy; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.IMemory; + +/** + * Simple value set. + *

    + * EXPERIMENTAL. This class or interface has been added as + * part of a work in progress. There is no guarantee that this API will + * work or that it will remain the same. + * + * @since 2.0 + * @author Gabor Bergmann + */ +public class SimpleUnaryTable extends AbstractIndexTable implements ITableWriterUnary.Table { + + protected IMemory values = CollectionsFactory.createMultiset(); // TODO use SetMemory if unique + + private boolean unique; + + /** + * @param unique + * client promises to only insert a given tuple with multiplicity one + */ + public SimpleUnaryTable(IInputKey inputKey, ITableContext tableContext, boolean unique) { + super(inputKey, tableContext); + this.unique = unique; + if (1 != inputKey.getArity()) + throw new IllegalArgumentException(inputKey.toString()); + } + + @Override + public void write(Direction direction, Value value) { + if (direction == Direction.INSERT) { + boolean changed = values.addOne(value); + if (unique && !changed) { + String msg = String.format( + "Error: trying to add duplicate value %s to the unique set %s. This indicates some errors in underlying model representation.", + value, getInputKey().getPrettyPrintableName()); + logError(msg); + } + if (changed && emitNotifications) { + deliverChangeNotifications(Tuples.staticArityFlatTupleOf(value), true); + } + } else { // DELETE + boolean changed = values.removeOne(value); + if (unique && !changed) { + String msg = String.format( + "Error: trying to remove duplicate value %s from the unique set %s. This indicates some errors in underlying model representation.", + value, getInputKey().getPrettyPrintableName()); + logError(msg); + } + if (changed && emitNotifications) { + deliverChangeNotifications(Tuples.staticArityFlatTupleOf(value), false); + } + } + } + + @Override + @SuppressWarnings("unchecked") + public boolean containsTuple(ITuple seed) { + return values.containsNonZero((Value) seed.get(0)); + } + + @Override + public int countTuples(TupleMask seedMask, ITuple seed) { + if (seedMask.getSize() == 0) { // unseeded + return values.size(); + } else { + @SuppressWarnings("unchecked") + Value value = (Value) seed.get(0); + return values.containsNonZero(value) ? 1 : 0; + } + } + + + @Override + public Optional estimateProjectionSize(TupleMask groupMask, Accuracy requiredAccuracy) { + // always exact count + if (groupMask.getSize() == 0) { + return values.isEmpty() ? Optional.of(0L) : Optional.of(1L); + } else { + return Optional.of((long)values.size()); + } + } + + + @Override + public Stream streamTuples(TupleMask seedMask, ITuple seed) { + if (seedMask.getSize() == 0) { // unseeded + return values.distinctValues().stream().map(Tuples::staticArityFlatTupleOf); + } else { + @SuppressWarnings("unchecked") + Value value = (Value) seed.get(0); + if (values.containsNonZero(value)) + return Stream.of(Tuples.staticArityFlatTupleOf(value)); + else + return Stream.empty(); + } + } + + @Override + public Iterable enumerateValues(TupleMask seedMask, ITuple seed) { + if (seedMask.getSize() == 0) { // unseeded + return values; + } else { + throw new IllegalArgumentException(seedMask.toString()); + } + } + @Override + public Stream streamValues(TupleMask seedMask, ITuple seed) { + if (seedMask.getSize() == 0) { // unseeded + return values.asStream(); + } else { + throw new IllegalArgumentException(seedMask.toString()); + } + } + + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/AbstractTuple.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/AbstractTuple.java new file mode 100644 index 00000000..a26d9193 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/AbstractTuple.java @@ -0,0 +1,136 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.tuple; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * Common implementation methods for immutable and volatile tuples. The class should not be used directly in client + * code, except for the definition of new tuple implementations. + * + * @author Zoltan Ujhelyi + * @since 1.7 + */ +public abstract class AbstractTuple implements ITuple { + + /** + * As the tuple is supposed to be immutable, do not modify the returned array. + * + * @return the array containing all elements of this Tuple + */ + @Override + public Object[] getElements() { + Object[] allElements = new Object[getSize()]; + for (int i = 0; i < allElements.length; ++i) + allElements[i] = get(i); + return allElements; + } + + /** + * @return the set containing all distinct elements of this Tuple, cast as type T + */ + @Override + @SuppressWarnings("unchecked") + public Set getDistinctElements() { + Set result = new HashSet(); + Object[] elements = getElements(); + for (Object object : elements) { + result.add((T) object); + } + return result; + } + + /** + * Calculates an inverted index of the elements of this pattern. For each element, the index of the (last) + * occurrence is calculated. + * + * @return the inverted index mapping each element of this pattern to its index in the array + */ + @Override + public Map invertIndex() { + Map result = new HashMap(); + for (int i = 0; i < getSize(); i++) + result.put(get(i), i); + return result; + } + + /** + * Calculates an inverted index of the elements of this pattern. For each element, the index of all of its + * occurrences is calculated. + * + * @return the inverted index mapping each element of this pattern to its index in the array + */ + @Override + public Map> invertIndexWithMupliplicity() { + Map> result = new HashMap>(); + for (int i = 0; i < getSize(); i++) { + Object value = get(i); + List indices = result.computeIfAbsent(value, v -> new ArrayList<>()); + indices.add(i); + } + return result; + } + + /** + * @since 1.7 + */ + protected IndexOutOfBoundsException raiseIndexingError(int index) { + return new IndexOutOfBoundsException( + String.format("No value at position %d for %s instance %s", index, getClass().getSimpleName(), this)); + } + + /** + * Compares the elements stored in this tuple to another tuple + */ + protected boolean internalEquals(ITuple other) { + if (getSize() != other.getSize()) + return false; + boolean result = true; + for (int i = 0; result && i < getSize(); ++i) { + Object ours = get(i); + Object theirs = other.get(i); + result = result && Objects.equals(ours, theirs); + } + return result; + } + + @Override + public String toString() { + StringBuilder s = new StringBuilder(); + s.append("T("); + for (Object o : getElements()) { + s.append(o == null ? "null" : o.toString()); + s.append(';'); + } + s.append(')'); + return s.toString(); + } + + /** + * @since 1.7 + */ + protected int doCalcHash() { + final int PRIME = 31; + int hash = 1; + for (int i = 0; i < getSize(); i++) { + hash = PRIME * hash; + Object element = get(i); + if (element != null) + hash += element.hashCode(); + } + return hash; + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/BaseFlatTuple.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/BaseFlatTuple.java new file mode 100644 index 00000000..6f46b763 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/BaseFlatTuple.java @@ -0,0 +1,20 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.tuple; + +/** + * Base class for all flat tuple implementations. + * Flat tuples store all elements locally (do not reference other tuples). + * + * @author Gabor Bergmann + * @since 1.7 + */ +public abstract class BaseFlatTuple extends Tuple { + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/BaseLeftInheritanceTuple.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/BaseLeftInheritanceTuple.java new file mode 100644 index 00000000..03f9ea89 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/BaseLeftInheritanceTuple.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.tuple; + +/** + * Common functionality of left inheritance tuple implementations. + * + *

    Left inheritance tuples inherit their first few elements from another tuple, + * and extend it with additional "local" elements. + * + * @author Gabor Bergmann + * @since 1.7 + */ +public abstract class BaseLeftInheritanceTuple extends Tuple { + + /** + * The number of elements that aren't stored locally, but inherited from an ancestor Tuple instead. + */ + protected final int inheritedIndex; + /** + * This object contains the same elements as the ancestor on the first inheritedIndex positions + */ + protected final Tuple ancestor; + + /** + * @param ancestor + */ + public BaseLeftInheritanceTuple(Tuple ancestor) { + super(); + this.ancestor = ancestor; + this.inheritedIndex = ancestor.getSize(); + } + + /** + * @return the number of local (non-inherited) elements + */ + public abstract int getLocalSize(); + + /** + * Optimized equals calculation (prediction: true, since hash values match) + */ + @Override + protected boolean internalEquals(ITuple other) { + if (other instanceof BaseLeftInheritanceTuple) { + BaseLeftInheritanceTuple blit = (BaseLeftInheritanceTuple) other; + if (blit.inheritedIndex == this.inheritedIndex) { + if (this.ancestor.equals(blit.ancestor)) { + return localEquals(blit); + } else return false; + } + } + return super.internalEquals(other); + } + + /** + * Checks the equivalence of local elements only, after ancestor tuple has been determined to be equal. + */ + protected abstract boolean localEquals(BaseLeftInheritanceTuple other); +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple.java new file mode 100644 index 00000000..8bbb0ac2 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.tuple; + +import java.util.Arrays; + +/** + * Default Tuple implementation, with statically unknown arity. + * @author Gabor Bergmann + */ +public final class FlatTuple extends BaseFlatTuple { + + /** + * Array of substituted values. DO NOT MODIFY! Use Constructor to build a new instance instead. + */ + private final Object[] elements; + + /** + * Creates a FlatTuple instance, fills it with the given array. + *

    Users should consider calling {@link Tuples#flatTupleOf(Object...)} instead to save memory on low-arity tuples. + * + * @param elements + * array of substitution values + */ + protected FlatTuple(Object... elements) { + this.elements = Arrays.copyOf(elements, elements.length); + calcHash(); + } + + @Override + public Object get(int index) { + return elements[index]; + } + + @Override + public int getSize() { + return elements.length; + } + + @Override + public Object[] getElements() { + return elements; + } + + @Override + protected boolean internalEquals(ITuple other) { + if (other instanceof FlatTuple) { + return Arrays.equals(elements, ((FlatTuple) other).elements); + } else + return super.internalEquals(other); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple0.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple0.java new file mode 100644 index 00000000..93f412b7 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple0.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.tuple; + +/** + * Flat tuple with statically known arity of 0. + * + * @author Gabor Bergmann + * @since 1.7 + * + */ +public final class FlatTuple0 extends BaseFlatTuple { + protected static final FlatTuple0 INSTANCE = new FlatTuple0(); + + private FlatTuple0() { + calcHash(); + } + + @Override + public int getSize() { + return 0; + } + + @Override + public Object get(int index) { + throw raiseIndexingError(index); + } + + private static final Object[] NULLARY_ARRAY = new Object[0]; + + @Override + public Object[] getElements() { + return NULLARY_ARRAY; + } + + @Override + protected boolean internalEquals(ITuple other) { + return 0 == other.getSize(); + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple1.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple1.java new file mode 100644 index 00000000..b3b1c312 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple1.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.tuple; + +import java.util.Objects; + +/** + * Flat tuple with statically known arity of 1. + * + * @author Gabor Bergmann + * @since 1.7 + * + */ +public final class FlatTuple1 extends BaseFlatTuple { + private final Object element0; + + protected FlatTuple1(Object element0) { + this.element0 = element0; + calcHash(); + } + + @Override + public int getSize() { + return 1; + } + + @Override + public Object get(int index) { + if (index == 0) return element0; + else throw raiseIndexingError(index); + } + + @Override + protected boolean internalEquals(ITuple other) { + return 1 == other.getSize() && + Objects.equals(element0, other.get(0)); + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple2.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple2.java new file mode 100644 index 00000000..2dcfd718 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple2.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.tuple; + +import java.util.Objects; + +/** + * Flat tuple with statically known arity of 2. + * + * @author Gabor Bergmann + * @since 1.7 + * + */ +public final class FlatTuple2 extends BaseFlatTuple { + private final Object element0; + private final Object element1; + + protected FlatTuple2(Object element0, Object element1) { + this.element0 = element0; + this.element1 = element1; + calcHash(); + } + + @Override + public int getSize() { + return 2; + } + + @Override + public Object get(int index) { + switch(index) { + case 0 : return element0; + case 1 : return element1; + default: throw raiseIndexingError(index); + } + } + + @Override + protected boolean internalEquals(ITuple other) { + return 2 == other.getSize() && + Objects.equals(element0, other.get(0)) && + Objects.equals(element1, other.get(1)); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple3.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple3.java new file mode 100644 index 00000000..50cee57e --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple3.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.tuple; + +import java.util.Objects; + +/** + * Flat tuple with statically known arity of 3. + * + * @author Gabor Bergmann + * @since 1.7 + * + */ +public final class FlatTuple3 extends BaseFlatTuple { + private final Object element0; + private final Object element1; + private final Object element2; + + protected FlatTuple3(Object element0, Object element1, Object element2) { + this.element0 = element0; + this.element1 = element1; + this.element2 = element2; + calcHash(); + } + + @Override + public int getSize() { + return 3; + } + + @Override + public Object get(int index) { + switch (index) { + case 0: return element0; + case 1: return element1; + case 2: return element2; + default: throw raiseIndexingError(index); + } + } + + @Override + protected boolean internalEquals(ITuple other) { + return 3 == other.getSize() && + Objects.equals(element0, other.get(0)) && + Objects.equals(element1, other.get(1)) && + Objects.equals(element2, other.get(2)); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple4.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple4.java new file mode 100644 index 00000000..024c94f4 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/FlatTuple4.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.tuple; + +import java.util.Objects; + +/** + * Flat tuple with statically known arity of 4. + * + * @author Gabor Bergmann + * @since 1.7 + * + */ +public final class FlatTuple4 extends BaseFlatTuple { + private final Object element0; + private final Object element1; + private final Object element2; + private final Object element3; + + protected FlatTuple4(Object element0, Object element1, Object element2, Object element3) { + this.element0 = element0; + this.element1 = element1; + this.element2 = element2; + this.element3 = element3; + calcHash(); + } + + @Override + public int getSize() { + return 4; + } + + @Override + public Object get(int index) { + switch(index) { + case 0: return element0; + case 1: return element1; + case 2: return element2; + case 3: return element3; + default: throw raiseIndexingError(index); + } + } + + @Override + protected boolean internalEquals(ITuple other) { + return 4 == other.getSize() && + Objects.equals(element0, other.get(0)) && + Objects.equals(element1, other.get(1)) && + Objects.equals(element2, other.get(2)) && + Objects.equals(element3, other.get(3)); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/IModifiableTuple.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/IModifiableTuple.java new file mode 100644 index 00000000..f5dab2a5 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/IModifiableTuple.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.tuple; + +/** + * A tuple that allows modifying the underlying value. Should not be used for non-volatile tuples. + * + * @author Zoltan Ujhelyi + * @since 1.7 + */ +public interface IModifiableTuple extends ITuple { + + /** + * Sets the selected value for a tuple + * + * @pre: 0 <= index < getSize() + * + */ + void set(int index, Object value); + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/ITuple.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/ITuple.java new file mode 100644 index 00000000..92014781 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/ITuple.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.tuple; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Represents both mutable and immutable tuples + * + * @author Zoltan Ujhelyi + * @since 1.7 + * + */ +public interface ITuple { + + /** + * @pre: 0 <= index < getSize() + * + * @return the element at the specified index + */ + Object get(int index); + + /** + * As the tuple is supposed to be immutable, do not modify the returned array. + * @return the array containing all elements of this Tuple + */ + Object[] getElements(); + + /** + * @return the set containing all distinct elements of this Tuple, cast as type T + */ + Set getDistinctElements(); + + /** + * @return number of elements + */ + int getSize(); + + /** + * Calculates an inverted index of the elements of this pattern. For each element, the index of the (last) + * occurrence is calculated. + * + * @return the inverted index mapping each element of this pattern to its index in the array + */ + Map invertIndex(); + + /** + * Calculates an inverted index of the elements of this pattern. For each element, the index of all of its + * occurrences is calculated. + * + * @return the inverted index mapping each element of this pattern to its index in the array + */ + Map> invertIndexWithMupliplicity(); + + Tuple toImmutable(); +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/LeftInheritanceTuple.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/LeftInheritanceTuple.java new file mode 100644 index 00000000..dcdfc376 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/LeftInheritanceTuple.java @@ -0,0 +1,172 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.tuple; + +import java.util.Arrays; +import java.util.Objects; + +/** + * + * Tuple that inherits another tuple on the left. + * + * @author Gabor Bergmann + * + */ +public final class LeftInheritanceTuple extends BaseLeftInheritanceTuple { + /** + * Array of substituted values above inheritedIndex. DO NOT MODIFY! Use Constructor to build a new instance instead. + */ + private final Object[] localElements; + + // + // /** + // * Creates a Tuple instance, fills it with the given array. + // * @pre: no elements are null + // * @param elements array of substitution values + // */ + // public Tuple(Object[] elements) + // { + // this.localElements = elements; + // this.ancestor=null; + // this.inheritedIndex = 0; + // calcHash(); + // } + + + + /** + * Creates a Tuple instance, lets it inherit from an ancestor, extends it with a given array. @pre: no elements are + * null + * + * @param localElements + * array of substitution values + */ + LeftInheritanceTuple(Tuple ancestor, Object[] localElements) { + super(ancestor); + this.localElements = localElements; + calcHash(); + } + + // + // /** + // * Creates a Tuple instance of size one, fills it with the given object. + // * @pre: o!=null + // * @param o the single substitution + // */ + // public Tuple(Object o) + // { + // localElements = new Object [1]; + // localElements[0] = o; + // this.ancestor=null; + // this.inheritedIndex = 0; + // calcHash(); + // } + // + // /** + // * Creates a Tuple instance of size two, fills it with the given objects. + // * @pre: o1!=null, o2!=null + // */ + // public Tuple(Object o1, Object o2) + // { + // localElements = new Object [2]; + // localElements[0] = o1; + // localElements[1] = o2; + // this.ancestor=null; + // this.inheritedIndex = 0; + // calcHash(); + // } + // + // /** + // * Creates a Tuple instance of size three, fills it with the given + // objects. + // * @pre: o1!=null, o2!=null, o3!=null + // */ + // public Tuple(Object o1, Object o2, Object o3) + // { + // localElements = new Object [3]; + // localElements[0] = o1; + // localElements[1] = o2; + // localElements[2] = o3; + // this.ancestor=null; + // this.inheritedIndex = 0; + // calcHash(); + // } + + /** + * @return number of elements + */ + public int getSize() { + return inheritedIndex + localElements.length; + } + + @Override + public int getLocalSize() { + return localElements.length; + } + + /** + * @pre: 0 <= index < getSize() + * + * @return the element at the specified index + */ + public Object get(int index) { + return (index < inheritedIndex) ? ancestor.get(index) : localElements[index - inheritedIndex]; + } + + /** + * Optimized hash calculation + */ + @Override + void calcHash() { + final int PRIME = 31; + cachedHash = ancestor.hashCode(); + for (int i = 0; i < localElements.length; i++) { + cachedHash = PRIME * cachedHash; + Object element = localElements[i]; + if (element != null) + cachedHash += element.hashCode(); + } + } + + /** + * Optimized equals calculation (prediction: true, since hash values match) + */ + @Override + protected boolean localEquals(BaseLeftInheritanceTuple other) { + if (other instanceof LeftInheritanceTuple) { + LeftInheritanceTuple lit = (LeftInheritanceTuple)other; + return Arrays.equals(this.localElements, lit.localElements); + } else { + if (localElements.length != other.getLocalSize()) + return false; + int index = inheritedIndex; + for (int i = 0; i indicesSorted and isNonrepeating may be OPTIONALLY given if known. + * @since 2.0 + */ + protected TupleMask(int[] indices, int sourceWidth, int[] indicesSorted, Boolean isNonrepeating) { + this.indices = indices; + this.sourceWidth = sourceWidth; + this.indicesSorted = indicesSorted; + this.isNonrepeating = isNonrepeating; + } + + /** + * Creates a TupleMask instance that selects given positions. + * The mask takes ownership of the array selectedIndices, the client must not modify it afterwards. + * + *

    indicesSorted and isNonrepeating may be OPTIONALLY given if known. + * @since 2.0 + */ + protected static TupleMask fromSelectedIndicesInternal( + int[] selectedIndices, int sourceArity, + int[] indicesSorted, Boolean isNonrepeating) + { + if (selectedIndices.length == 0) // is it nullary? + return new TupleMask0(sourceArity); + + // is it identity? + boolean identity = sourceArity == selectedIndices.length; + if (identity) { + for (int k=0; k < sourceArity; ++k) { + if (selectedIndices[k] != k) { + identity = false; + break; + } + } + } + if (identity) + return new TupleMaskIdentity(selectedIndices, sourceArity); + + // generic case + return new TupleMask(selectedIndices, sourceArity, indicesSorted, isNonrepeating); + } + + /** + * Creates a TupleMask instance that selects given positions in monotonically increasing order. + * The mask takes ownership of the array selectedIndices, the client must not modify it afterwards. + * @since 2.0 + */ + protected static TupleMask fromSelectedMonotonicIndicesInternal(int[] selectedIndices, int sourceArity) + { + return fromSelectedIndicesInternal(selectedIndices, sourceArity, selectedIndices /* also sorted */, true); + } + + /** + * Creates a TupleMask instance of the given size that maps the first 'size' elements intact + */ + public static TupleMask linear(int size, int sourceWidth) { + if (size == sourceWidth) return new TupleMaskIdentity(sourceWidth); + int[] indices = constructLinearSequence(size); + return fromSelectedMonotonicIndicesInternal(indices, sourceWidth); + } + + /** + * An array containing the first {@link size} nonnegative integers in order + * @since 2.0 + */ + protected static int[] constructLinearSequence(int size) { + int[] indices = new int[size]; + for (int i = 0; i < size; i++) + indices[i] = i; + return indices; + } + + /** + * Creates a TupleMask instance of the given size that maps every single element intact + */ + public static TupleMask identity(int size) { + return new TupleMaskIdentity(size); + } + + /** + * Creates a TupleMask instance of the given size that does not emit output. + */ + public static TupleMask empty(int sourceWidth) { + return linear(0, sourceWidth); + } + + /** + * Creates a TupleMask instance that maps the tuple intact save for a single element at the specified index which is + * omitted + */ + public static TupleMask omit(int omission, int sourceWidth) { + int size = sourceWidth - 1; + int[] indices = new int[size]; + for (int i = 0; i < omission; i++) + indices[i] = i; + for (int i = omission; i < size; i++) + indices[i] = i + 1; + return fromSelectedMonotonicIndicesInternal(indices, sourceWidth); + } + + + /** + * Creates a TupleMask instance that selects positions where keep is true + * @since 1.7 + */ + public static TupleMask fromKeepIndicators(boolean[] keep) { + int size = 0; + for (int k = 0; k < keep.length; ++k) + if (keep[k]) + size++; + if (size == keep.length) return new TupleMaskIdentity(size); + int[] indices = new int[size]; + int l = 0; + for (int k = 0; k < keep.length; ++k) + if (keep[k]) + indices[l++] = k; + return fromSelectedMonotonicIndicesInternal(indices, keep.length); + } + + /** + * Creates a TupleMask instance that selects given positions. + * @since 1.7 + */ + public static TupleMask fromSelectedIndices(int sourceArity, Collection selectedIndices) { + int[] selected = integersToIntArray(selectedIndices); + return fromSelectedIndicesInternal(selected, sourceArity, null, null); + } + /** + * Creates a TupleMask instance that selects given positions. + * @since 1.7 + */ + public static TupleMask fromSelectedIndices(int sourceArity, int[] selectedIndices) { + return fromSelectedIndicesInternal(Arrays.copyOf(selectedIndices, selectedIndices.length), sourceArity, null, null); + } + /** + * Creates a TupleMask instance that selects non-null positions of a given tuple + * @since 1.7 + */ + public static TupleMask fromNonNullIndices(ITuple tuple) { + List indices = new ArrayList<>(); + for (int i=0; i < tuple.getSize(); i++) { + if (tuple.get(i) != null) { + indices.add(i); + } + } + if (indices.size() == tuple.getSize()) return new TupleMaskIdentity(indices.size()); + return fromSelectedMonotonicIndicesInternal(integersToIntArray(indices), tuple.getSize()); + } + /** + * @since 1.7 + */ + public static int[] integersToIntArray(Collection selectedIndices) { + int[] selected = new int[selectedIndices.size()]; + int k=0; + for (Integer integer : selectedIndices) { + selected[k++] = integer; + } + return selected; + } + + + /** + * Creates a TupleMask instance that moves an element from one index to other, shifting the others if neccessary. + */ + public static TupleMask displace(int from, int to, int sourceWidth) { + if (from == to) return new TupleMaskIdentity(sourceWidth); + int[] indices = new int[sourceWidth]; + for (int i = 0; i < sourceWidth; i++) + if (i == to) + indices[i] = from; + else if (i >= from && i < to) + indices[i] = i + 1; + else if (i > to && i <= from) + indices[i] = i - 1; + else + indices[i] = i; + return fromSelectedIndicesInternal(indices, sourceWidth, null, true); + } + + /** + * Creates a TupleMask instance that selects a single element of the tuple. + */ + public static TupleMask selectSingle(int selected, int sourceWidth) { + int[] indices = { selected }; + return fromSelectedMonotonicIndicesInternal(indices, sourceWidth); + } + + /** + * Creates a TupleMask instance that selects whatever is selected by left, and appends whatever is selected by + * right. PRE: left and right have the same sourcewidth + */ + public static TupleMask append(TupleMask left, TupleMask right) { + int leftLength = left.indices.length; + int rightLength = right.indices.length; + int[] indices = new int[leftLength + rightLength]; + for (int i = 0; i < leftLength; ++i) + indices[i] = left.indices[i]; + for (int i = 0; i < rightLength; ++i) + indices[i + leftLength] = right.indices[i]; + return fromSelectedIndicesInternal(indices, left.sourceWidth, null, null); + } + + /** + * Generates indicesSorted from indices on demand + */ + void ensureIndicesSorted() { + if (indicesSorted == null) { + indicesSorted = new int[indices.length]; + List list = new LinkedList(); + for (int i = 0; i < indices.length; ++i) + list.add(indices[i]); + java.util.Collections.sort(list); + int i = 0; + for (Integer a : list) + indicesSorted[i++] = a; + } + } + + + + /** + * Returns the first index of the source that is not selected by the mask, or empty if all indices are selected. + *

    PRE: mask indices are all different + * @since 2.0 + */ + public OptionalInt getFirstOmittedIndex() { + ensureIndicesSorted(); + int column = 0; + while (column < getSize() && indicesSorted[column] == column) column++; + if (column < getSourceWidth()) return OptionalInt.of(column); + else return OptionalInt.empty(); + } + + + /** + * Returns a selected masked value from the selected tuple. + * @pre: 0 <= index < getSize() + * @since 1.7 + */ + public Object getValue(ITuple original, int index) { + return original.get(indices[index]); + } + + /** + * Sets the selected value in the original tuple based on the mask definition + * + * @pre: 0 <= index < getSize() + * @since 1.7 + */ + public void set(IModifiableTuple tuple, int index, Object value) { + tuple.set(indices[index], value); + } + + /** + * Generates an immutable, masked view of the original tuple. + *

    The new tuple will have arity {@link #getSize()}, + * and will consist of the elements of the original tuple, at positions indicated by this mask. + * @since 1.7 + */ + public Tuple transform(ITuple original) { + switch (indices.length) { + case 0: + return FlatTuple0.INSTANCE; + case 1: + return new FlatTuple1(original.get(indices[0])); + case 2: + return new FlatTuple2(original.get(indices[0]), original.get(indices[1])); + case 3: + return new FlatTuple3(original.get(indices[0]), original.get(indices[1]), original.get(indices[2])); + case 4: + return new FlatTuple4(original.get(indices[0]), original.get(indices[1]), original.get(indices[2]), original.get(indices[3])); + default: + Object signature[] = new Object[indices.length]; + for (int i = 0; i < indices.length; ++i) + signature[i] = original.get(indices[i]); + return new FlatTuple(signature); + } + } + + /** + * @return true iff no two selected indices are the same + * @since 2.0 + */ + public boolean isNonrepeating() { + if (isNonrepeating == null) { + ensureIndicesSorted(); + int previous = -1; + int i; + for (i = 0; i < sourceWidth && previous != indicesSorted[i]; ++i) { + previous = indicesSorted[i]; + } + isNonrepeating = (i == sourceWidth); // if not, stopped due to detected repetition + } + return isNonrepeating; + } + + /** + * Returns a tuple `result` that satisfies `this.transform(result).equals(masked)`. Positions of the result tuple + * that are not determined this way will be filled with null. + * + * @pre: all indices of the mask must be different, i.e {@link #isNonrepeating()} must return true + * @since 1.7 + */ + public Tuple revertFrom(ITuple masked) { + Object[] signature = new Object[sourceWidth]; + for (int i = 0; i < indices.length; ++i) + signature[indices[i]] = masked.get(i); + return Tuples.flatTupleOf(signature); + } + + /** + * Returns a tuple `result`, same arity as the original tuple, that satisfies + * `this.transform(result).equals(this.transform(tuple))`. + * Positions of the result tuple that are not determined this way will be filled with null. + *

    In other words, a copy of the original tuple is returned, + * with null substituted at each position that is not selected by this mask. + * + * @pre: all indices of the mask must be different, i.e {@link #isNonrepeating()} must return true + * @since 2.1 + */ + public Tuple keepSelectedIndices(ITuple original) { + Object[] signature = new Object[sourceWidth]; + for (int i = 0; i < indices.length; ++i) + signature[indices[i]] = original.get(indices[i]); + return Tuples.flatTupleOf(signature); + } + + /** + * Generates an immutable, masked view of the original tuple. + *

    The list will have arity {@link #getSize()}, + * and will consist of the elements of the original tuple, at positions indicated by this mask. + */ + public List transform(List original) { + List signature = new ArrayList(indices.length); + for (int i = 0; i < indices.length; ++i) + signature.add(original.get(indices[i])); + return signature; + } + + /** + * Transforms a given mask directly, instead of transforming tuples that were transformed by the other mask. + * + * @return a mask that cascades the effects this mask after the mask provided as parameter. + */ + public TupleMask transform(TupleMask mask) { + int[] cascadeIndices = new int[indices.length]; + for (int i = 0; i < indices.length; ++i) + cascadeIndices[i] = mask.indices[indices[i]]; + return fromSelectedIndicesInternal(cascadeIndices, mask.sourceWidth, null, null); + } + + // /** + // * Generates a complementer mask that maps those elements that were + // untouched by the original mask. + // * Ordering is left intact. + // * A Tuple is used for reference concerning possible equalities among + // elements. + // */ + // public TupleMask complementer(Tuple reference) + // { + // HashSet touched = new HashSet(); + // LinkedList untouched = new LinkedList(); + // + // for (int index : indices) touched.add(reference.get(index)); + // for (int index=0; index"); + for (int i : indices) { + s.append(i); + s.append(','); + } + s.append(')'); + return s.toString(); + } + + /** + * Returns the size of the masked tuples described by this mask + * @since 1.7 + */ + public int getSize() { + return indices.length; + } + + /** + * Returns the size of the original tuples handled by this mask + * @since 1.7 + */ + public int getSourceWidth() { + return sourceWidth; + } + + + /** + * @return true iff this mask is a no-op + * @since 2.0 + */ + public boolean isIdentity() { + // Contract: if identity mask, a specialized subclass is constructed instead + return false; + } + + /** + * Transforms the given list by applying the mask and putting all results into a set; keeping only a single element + * in case of the mapping result in duplicate values. + * + * @since 1.7 + */ + public Set transformUnique(List original) { + Set signature = new HashSet<>(); + for (int i = 0; i < indices.length; ++i) + signature.add(original.get(indices[i])); + return signature; + } + + /** + * @return the list of selected indices + * @since 2.1 + */ + public List getIndicesAsList() { + List result = new ArrayList(indices.length); + for (int i = 0; i < indices.length; ++i) + result.add(indices[i]); + return result; + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/TupleMask0.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/TupleMask0.java new file mode 100644 index 00000000..5a0c79ff --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/TupleMask0.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.tuple; + +import java.util.Collections; +import java.util.List; + +/** + * @author Gabor Bergmann + * @since 1.7 + */ +public final class TupleMask0 extends TupleMask { + + private final static int[] EMPTY_ARRAY = {}; + + /** + * PRE: indices.length == 0 + */ + TupleMask0(int sourceWidth) { + super(EMPTY_ARRAY, sourceWidth, EMPTY_ARRAY, true); + } + + @Override + public List transform(List original) { + return Collections.emptyList(); + } + + @Override + public Tuple transform(ITuple original) { + return Tuples.staticArityFlatTupleOf(); + } + + @Override + public TupleMask transform(TupleMask mask) { + return new TupleMask0(mask.sourceWidth); + } + + @Override + public Tuple combine(Tuple unmasked, Tuple masked, boolean useInheritance, boolean asComplementer) { + if (asComplementer) + return unmasked; + else + return super.combine(unmasked, masked, useInheritance, asComplementer); + } + + @Override + public boolean isIdentity() { + return 0 == sourceWidth; + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/TupleMaskIdentity.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/TupleMaskIdentity.java new file mode 100644 index 00000000..62746587 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/TupleMaskIdentity.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.tuple; + +import java.util.List; + +/** + * @author Gabor Bergmann + * @since 1.7 + */ +public final class TupleMaskIdentity extends TupleMask { + + TupleMaskIdentity(int sourceWidth) { + this(constructLinearSequence(sourceWidth), sourceWidth); + } + TupleMaskIdentity(int[] indices, int sourceWidth) { + super(indices, sourceWidth, indices, true); + } + + @Override + public List transform(List original) { + return original; + } + + @Override + public Tuple transform(ITuple original) { + return original.toImmutable(); + } + + @Override + public TupleMask transform(TupleMask mask) { + return mask; + } + + @Override + public Tuple revertFrom(ITuple masked) { + return masked.toImmutable(); + } + + @Override + public boolean isIdentity() { + return true; + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/TupleValueProvider.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/TupleValueProvider.java new file mode 100644 index 00000000..79193516 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/TupleValueProvider.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.tuple; + +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.psystem.IValueProvider; + +/** + * @author Zoltan Ujhelyi + * @since 1.7 + */ +public class TupleValueProvider implements IValueProvider { + + final ITuple tuple; + final Map indexMapping; + + /** + * Wraps a tuple with an index mapping + * @param tuple + * @param indexMapping + */ + public TupleValueProvider(ITuple tuple, Map indexMapping) { + super(); + this.tuple = tuple; + this.indexMapping = indexMapping; + } + + @Override + public Object getValue(String variableName) { + Integer index = indexMapping.get(variableName); + if (index == null) { + throw new IllegalArgumentException(String.format("Variable %s is not present in mapping.", variableName)); + } + Object value = tuple.get(index); + if (value == null) { + throw new IllegalArgumentException(String.format("Variable %s is not found using index %d.", variableName, index)); + } + return value; + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/Tuples.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/Tuples.java new file mode 100644 index 00000000..5e41d7d8 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/Tuples.java @@ -0,0 +1,157 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.tuple; + +/** + * Common static factory utilities for tuples. + * + * @author Gabor Bergmann + * @since 1.7 + */ +public class Tuples { + + private Tuples() { + // Empty utility class constructor + } + + /** + * Creates a flat tuple consisting of the given elements. + * For low-arity tuples, specialized implementations + * (such as {@link FlatTuple2}) will be instantiated. + * + *

    In case the exact arity is statically known, + * it may be more efficient for the client to instantiate + * the appropriate specialized implementation + * (via {@link #staticArityFlatTupleOf(Object, Object)} etc. + * or {@link #wideFlatTupleOf(Object...)}), + * instead of invoking this method. + * This method does a runtime arity check, and therefore + * also appropriate if the arity is determined at runtime. + */ + public static Tuple flatTupleOf(Object... elements) { + switch (elements.length) { + case 0: + return FlatTuple0.INSTANCE; + case 1: + return new FlatTuple1(elements[0]); + case 2: + return new FlatTuple2(elements[0], elements[1]); + case 3: + return new FlatTuple3(elements[0], elements[1], elements[2]); + case 4: + return new FlatTuple4(elements[0], elements[1], elements[2], elements[3]); + default: + return new FlatTuple(elements); + } + } + /** + * Creates a left inheritance tuple that extends an ancestor tuple + * by the given "local" elements. + * For locally low-arity tuples, specialized implementations + * (such as {@link LeftInheritanceTuple2}) will be instantiated. + * + *

    In case the exact arity is statically known, + * it may be more efficient for the client to instantiate + * the appropriate specialized implementation + * (via {@link #staticArityLeftInheritanceTupleOf(Object, Object)} etc. + * or {@link #wideLeftInheritanceTupleOf(Object...)}), + * instead of invoking this method. + * This method does a runtime arity check, and therefore + * also appropriate if the arity is determined at runtime. + */ + public static Tuple leftInheritanceTupleOf(Tuple ancestor, Object... localElements) { + switch (localElements.length) { + case 0: + return ancestor; + case 1: + return new LeftInheritanceTuple1(ancestor, localElements[0]); + case 2: + return new LeftInheritanceTuple2(ancestor, localElements[0], localElements[1]); + case 3: + return new LeftInheritanceTuple3(ancestor, localElements[0], localElements[1], localElements[2]); + case 4: + return new LeftInheritanceTuple4(ancestor, localElements[0], localElements[1], localElements[2], localElements[3]); + default: + return new LeftInheritanceTuple(ancestor, localElements); + } + } + + /** + * Creates a flat tuple consisting of no elements. + */ + public static Tuple staticArityFlatTupleOf() { + return FlatTuple0.INSTANCE; + } + /** + * Creates a flat tuple consisting of the given single element. + */ + public static Tuple staticArityFlatTupleOf(Object element) { + return new FlatTuple1(element); + } + /** + * Creates a flat tuple consisting of the given elements. + */ + public static Tuple staticArityFlatTupleOf(Object element0, Object element1) { + return new FlatTuple2(element0, element1); + } + /** + * Creates a flat tuple consisting of the given elements. + */ + public static Tuple staticArityFlatTupleOf(Object element0, Object element1, Object element2) { + return new FlatTuple3(element0, element1, element2); + } + /** + * Creates a flat tuple consisting of the given elements. + */ + public static Tuple staticArityFlatTupleOf(Object element0, Object element1, Object element2, Object element3) { + return new FlatTuple4(element0, element1, element2, element3); + } + /** + * Creates a flat tuple consisting of the given elements. + *

    Invoke this only if it is statically known that the tuple will be wide. + * Otherwise, use {@link #flatTupleOf(Object...)}. + */ + public static Tuple wideFlatTupleOf(Object... elements) { + return new FlatTuple(elements); + } + + /** + * Creates a left inheritance tuple consisting of the given single local element. + */ + public static Tuple staticArityLeftInheritanceTupleOf(Tuple ancestor, Object element) { + return new LeftInheritanceTuple1(ancestor, element); + } + /** + * Creates a left inheritance tuple consisting of the given local elements. + */ + public static Tuple staticArityLeftInheritanceTupleOf(Tuple ancestor, Object element0, Object element1) { + return new LeftInheritanceTuple2(ancestor, element0, element1); + } + /** + * Creates a left inheritance tuple consisting of the given local elements. + */ + public static Tuple staticArityLeftInheritanceTupleOf(Tuple ancestor, Object element0, Object element1, Object element2) { + return new LeftInheritanceTuple3(ancestor, element0, element1, element2); + } + /** + * Creates a left inheritance tuple consisting of the given local elements. + */ + public static Tuple staticArityLeftInheritanceTupleOf(Tuple ancestor, Object element0, Object element1, Object element2, Object element3) { + return new LeftInheritanceTuple4(ancestor, element0, element1, element2, element3); + } + /** + * Creates a left inheritance tuple consisting of the given local elements. + *

    Invoke this only if it is statically known that the tuple will be wide. + * Otherwise, use {@link #leftInheritanceTupleOf(Tuple, Object...)}. + */ + public static Tuple wideLeftInheritanceTupleOf(Tuple ancestor, Object... elements) { + return new LeftInheritanceTuple(ancestor, elements); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/VolatileMaskedTuple.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/VolatileMaskedTuple.java new file mode 100644 index 00000000..f683d544 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/VolatileMaskedTuple.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.tuple; + +import tools.refinery.viatra.runtime.matchers.util.Preconditions; + +/** + * This class provides a volatile tuple view with a given mask of a given tuple instance. If the masked tuple changes, + * the view updates as well. + * + * @author Zoltan Ujhelyi + * @since 1.7 + * + */ +public class VolatileMaskedTuple extends VolatileTuple { + + protected final TupleMask mask; + protected ITuple source; + + public VolatileMaskedTuple(ITuple source, TupleMask mask) { + this.source = source; + this.mask = mask; + } + + public VolatileMaskedTuple(TupleMask mask) { + this(null, mask); + } + + public void updateTuple(ITuple newSource) { + source = newSource; + } + + @Override + public Object get(int index) { + Preconditions.checkState(source != null, "Source tuple is not set."); + return mask.getValue(source, index); + } + + @Override + public int getSize() { + return mask.getSize(); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/VolatileModifiableMaskedTuple.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/VolatileModifiableMaskedTuple.java new file mode 100644 index 00000000..92306c6e --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/VolatileModifiableMaskedTuple.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.tuple; + +import tools.refinery.viatra.runtime.matchers.util.Preconditions; + +/** + * A masked tuple implementation that allows modifying the backing tuple. + * @author Zoltan Ujhelyi + * @since 1.7 + * + */ +public class VolatileModifiableMaskedTuple extends VolatileMaskedTuple implements IModifiableTuple { + + private IModifiableTuple modifiableTuple; + + public VolatileModifiableMaskedTuple(IModifiableTuple source, TupleMask mask) { + super(source, mask); + modifiableTuple = source; + } + + public VolatileModifiableMaskedTuple(TupleMask mask) { + this(null, mask); + } + + @Override + public void updateTuple(ITuple newSource) { + Preconditions.checkArgument(newSource instanceof IModifiableTuple, "Provided tuple does not support updates"); + this.updateTuple((IModifiableTuple)newSource); + } + + public void updateTuple(IModifiableTuple newSource) { + super.updateTuple(newSource); + modifiableTuple = newSource; + } + + @Override + public void set(int index, Object value) { + mask.set(modifiableTuple, index, value); + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/VolatileTuple.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/VolatileTuple.java new file mode 100644 index 00000000..699105a5 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/tuple/VolatileTuple.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2010-2017 Zoltan Ujhelyi, IncQuery Labs + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.tuple; + +/** + * Mutable tuple without explicit modification commands. In practical terms, the values stored in a volatile tuple can + * be changed without any notification. + * + * @author Zoltan Ujhelyi + * @since 1.7 + * + */ +public abstract class VolatileTuple extends AbstractTuple { + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof ITuple)) + return false; + final ITuple other = (ITuple) obj; + return internalEquals(other); + } + + @Override + public int hashCode() { + return doCalcHash(); + } + + /** + * Creates an immutable tuple from the values stored in the tuple. The created tuple will not be updated when the + * current tuple changes. + */ + @Override + public Tuple toImmutable() { + return Tuples.flatTupleOf(getElements()); + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/Accuracy.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/Accuracy.java new file mode 100644 index 00000000..338990ab --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/Accuracy.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util; + +/** + * The degree of accuracy of a cardinality estimate + * @author Gabor Bergmann + * @since 2.1 + */ +public enum Accuracy { + EXACT_COUNT, + BEST_UPPER_BOUND, + BEST_LOWER_BOUND, + APPROXIMATION; + + /** + * Partial order comparison. + */ + public boolean atLeastAsPreciseAs(Accuracy other) { + switch (this) { + case EXACT_COUNT: return true; + case APPROXIMATION: return APPROXIMATION == other; + case BEST_UPPER_BOUND: return BEST_UPPER_BOUND == other || APPROXIMATION == other; + case BEST_LOWER_BOUND: return BEST_LOWER_BOUND == other || APPROXIMATION == other; + default: throw new IllegalArgumentException(); + } + } + + /** + * @return another accuracy value that is anti-monotonic to this one, + * i.e. an accuracy that should be used in the denominator to obtain a fraction with this accuracy + */ + public Accuracy reciprocal() { + switch(this) { + case APPROXIMATION: return APPROXIMATION; + case BEST_UPPER_BOUND: return BEST_LOWER_BOUND; + case BEST_LOWER_BOUND: return BEST_UPPER_BOUND; + case EXACT_COUNT: return EXACT_COUNT; + default: throw new IllegalArgumentException(); + } + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/Clearable.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/Clearable.java new file mode 100644 index 00000000..1b09aec6 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/Clearable.java @@ -0,0 +1,23 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.util; + +/** + * @author Gabor Bergmann + * @since 1.7 + * An instance of clearable pattern memory. + */ +public interface Clearable { + /** + * Clear all partial matchings stored in memory + * + */ + void clear(); +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/CollectionsFactory.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/CollectionsFactory.java new file mode 100644 index 00000000..590a1ec3 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/CollectionsFactory.java @@ -0,0 +1,188 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.function.Function; + +/** + * Factory class used as an accessor to Collections implementations. + * @author istvanrath + */ +public final class CollectionsFactory +{ + + /** + * Instantiates a new empty map. + * @since 1.7 + */ + public static Map createMap() { + return FRAMEWORK.createMap(); + } + + /** + * Instantiates a new map with the given initial contents. + * @since 1.7 + */ + public static Map createMap(Map initial) { + return FRAMEWORK.createMap(initial); + } + + /** + * Instantiates a new tree map. + * @since 2.3 + */ + public static TreeMap createTreeMap() { + return FRAMEWORK.createTreeMap(); + } + + /** + * Instantiates a new empty set. + * @since 1.7 + */ + public static Set createSet() { + return FRAMEWORK.createSet(); + } + + /** + * Instantiates a new set with the given initial contents. + * @since 1.7 + */ + public static Set createSet(Collection initial) { + return FRAMEWORK.createSet(initial); + } + + /** + * Instantiates an empty set; the key parameter is used to allow using this as a method reference as a + * {@link Function}, e.g. in {@link Map#computeIfAbsent(Object, Function)}. + * + * @param key + * the value of this parameter is ignored + * @since 2.0 + */ + public static Set emptySet(Object key) { + return FRAMEWORK.createSet(); + } + + /** + * Instantiates a new empty multiset. + * @since 1.7 + */ + public static IMultiset createMultiset() { + return FRAMEWORK.createMultiset(); + } + + /** + * Instantiates an empty multiset; the key parameter is used to allow using this as a method reference as a + * {@link Function}, e.g. in {@link Map#computeIfAbsent(Object, Function)}. + * + * @param key + * the value of this parameter is ignored + * @since 2.0 + */ + public static IMultiset emptyMultiset(Object key) { + return FRAMEWORK.createMultiset(); + } + + /** + * Instantiates a new empty delta bag. + * @since 1.7 + */ + public static IDeltaBag createDeltaBag() { + return FRAMEWORK.createDeltaBag(); + } + + /** + * Instantiates a new list that is optimized for registering observers / callbacks. + * @since 1.7 + */ + public static List createObserverList() { + return FRAMEWORK.createObserverList(); + } + + /** + * Instantiates a size-optimized multimap from keys to sets of values. + *

    For a single key, many values can be associated according to the given bucket semantics. + *

    The keys and values are stored as type fromKeys resp. ofValues; + * currently Object.class and Long.class are supported. + * @since 2.0 + */ + public static IMultiLookup createMultiLookup( + Class fromKeys, MemoryType toBuckets, Class ofValues) { + return FRAMEWORK.createMultiLookup(fromKeys, toBuckets, ofValues); + } + + /** + * Instantiates a memory storing values. + *

    For a single key, many values can be associated according to the given memory semantics. + *

    The values are stored as type 'values'; + * currently Object.class and Long.class are supported. + * @since 2.0 + */ + public static IMemory createMemory( + Class values, MemoryType memoryType) { + return FRAMEWORK.createMemory(values, memoryType); + } + + /** + * The type of {@link IMemory} + * @since 2.0 + * TODO add delta as type + */ + public enum MemoryType { + /** + * A single key-value pair is stored at most once + */ + SETS, + /** + * Duplicate key-value pairs allowed + */ + MULTISETS + } + + /** + * The collections framework of the current configuration. + * @since 1.7 + */ + private static final ICollectionsFramework FRAMEWORK = new EclipseCollectionsFactory(); + + /** + * Interface abstracting over a collections technology that provides custom collection implementations. + * @since 1.7 + */ + public static interface ICollectionsFramework { + + public abstract Map createMap(); + public abstract Map createMap(Map initial); + /** + * @since 2.3 + */ + public abstract TreeMap createTreeMap(); + public abstract Set createSet(); + public abstract Set createSet(Collection initial); + public abstract IMultiset createMultiset(); + public abstract IDeltaBag createDeltaBag(); + public abstract List createObserverList(); + + /** + * @since 2.0 + */ + public abstract IMultiLookup createMultiLookup( + Class fromKeys, MemoryType toBuckets, Class ofValues); + /** + * @since 2.0 + */ + public abstract IMemory createMemory(Class values, MemoryType memoryType); + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/Direction.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/Direction.java new file mode 100644 index 00000000..88f7ec00 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/Direction.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util; + +/** + * Indicates whether a propagated update event signals the insertion or deletion of an element + * + * @author Gabor Bergmann + */ +public enum Direction { + INSERT, DELETE; + + /** + * @since 2.4 + */ + public Direction opposite() { + switch (this) { + case INSERT: + return DELETE; + default: + return INSERT; + } + } + + /** + * @since 2.4 + */ + public char asSign() { + switch (this) { + case INSERT: + return '+'; + default: + return '-'; + } + } + + /** + * Returns the direction that is the product of this direction and the other direction. + * + * DELETE x DELETE = INSERT + * DELETE x INSERT = DELETE + * INSERT x DELETE = DELETE + * INSERT x INSERT = INSERT + * @since 2.4 + */ + public Direction multiply(final Direction other) { + switch (this) { + case DELETE: + return other.opposite(); + default: + return other; + } + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsBagMemory.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsBagMemory.java new file mode 100644 index 00000000..e24b2448 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsBagMemory.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.matchers.util; + +import java.util.Iterator; +import java.util.Set; +import java.util.function.BiConsumer; + +import org.eclipse.collections.impl.map.mutable.primitive.ObjectIntHashMap; + +/** + * Eclipse Collections-based multiset for tuples. Can contain duplicate occurrences of the same matching. + * + *

    Inherits Eclipse Collections' Object-to-Int primitive hashmap and counts the number of occurrences of each value. + * Element is deleted if # of occurences drops to 0. + * + * @author Gabor Bergmann. + * @since 1.7 + * @noreference + */ +public abstract class EclipseCollectionsBagMemory extends ObjectIntHashMap implements IMemory { + + public EclipseCollectionsBagMemory() { + super(); + } + + @Override + public int getCount(T value) { + return super.getIfAbsent(value, 0); + } + @Override + public int getCountUnsafe(Object value) { + return super.getIfAbsent(value, 0); + } + @Override + public boolean containsNonZero(T value) { + return super.containsKey(value); + } + @Override + public boolean containsNonZeroUnsafe(Object value) { + return super.containsKey(value); + } + + @Override + public void clearAllOf(T value) { + super.remove(value); + } + + + @Override + public Iterator iterator() { + return super.keySet().iterator(); + } + + @Override + public String toString() { + return "TM" + super.toString(); + } + + @Override + public Set distinctValues() { + return super.keySet(); + } + + @Override + public void forEachEntryWithMultiplicities(BiConsumer entryConsumer) { + super.forEachKeyValue(entryConsumer::accept); + } + + @Override + public int hashCode() { + return IMemoryView.hashCode(this); + } + @Override + public boolean equals(Object obj) { + return IMemoryView.equals(this, obj); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsDeltaBag.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsDeltaBag.java new file mode 100644 index 00000000..94ec33cd --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsDeltaBag.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util; + +/** + * @author Gabor Bergmann + * @since 1.7 + */ +public class EclipseCollectionsDeltaBag extends EclipseCollectionsBagMemory implements IDeltaBag { + + @Override + public boolean addOne(T value) { + return addSigned(value, +1); + } + + @Override + public boolean addSigned(T value, int count) { + int oldCount = super.getIfAbsent(value, 0); + int newCount = oldCount + count; + + boolean becomesZero = newCount == 0; + if (becomesZero) + super.removeKey(value); + else + super.put(value, newCount); + + return becomesZero || oldCount == 0; + } + + + @Override + public boolean removeOne(T value) { + return addSigned(value, -1); + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsFactory.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsFactory.java new file mode 100644 index 00000000..5a623c9b --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsFactory.java @@ -0,0 +1,159 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +import org.eclipse.collections.api.map.MutableMap; +import org.eclipse.collections.impl.factory.Maps; +import org.eclipse.collections.impl.factory.Sets; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.ICollectionsFramework; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType; + +/** + * @author Gabor Bergmann + * @since 1.7 + * @noreference This class is not intended to be referenced by clients. + */ +public class EclipseCollectionsFactory implements ICollectionsFramework { + + @Override + public Map createMap() { + return Maps.mutable.empty(); + } + + @Override + public Map createMap(Map initial) { + MutableMap result = Maps.mutable.ofInitialCapacity(initial.size()); + result.putAll(initial); + return result; + } + + @Override + public TreeMap createTreeMap() { + // eclipse collections is doing the same + return new TreeMap<>(); + } + + @Override + public Set createSet() { + return Sets.mutable.empty(); + } + + @Override + public Set createSet(Collection initial) { + return Sets.mutable.ofAll(initial); + } + + @Override + public IMultiset createMultiset() { + return new EclipseCollectionsMultiset(); + } + + @Override + public IDeltaBag createDeltaBag() { + return new EclipseCollectionsDeltaBag(); + } + + @Override + public List createObserverList() { + return new ArrayList(1); // keep concurrent modification exceptions for error detection + // Lists.mutable.empty + + } + + @Override + @SuppressWarnings({ "unchecked", "rawtypes" }) + public IMultiLookup createMultiLookup( + Class fromKeys, + MemoryType toBuckets, + Class ofValues) + { + boolean longKeys = Long.class.equals(fromKeys); + boolean objectKeys = Object.class.equals(fromKeys); + if (! (longKeys || objectKeys)) throw new IllegalArgumentException(fromKeys.getName()); + boolean longValues = Long.class.equals(ofValues); + boolean objectValues = Object.class.equals(ofValues); + if (! (longValues || objectValues)) throw new IllegalArgumentException(ofValues.getName()); + + if (longKeys) { // K == java.lang.Long + if (longValues) { // V == java.lang.Long + switch(toBuckets) { + case MULTISETS: + return (IMultiLookup) new EclipseCollectionsMultiLookup.FromLongs.ToMultisets.OfLongs(); + case SETS: + return (IMultiLookup) new EclipseCollectionsMultiLookup.FromLongs.ToSets.OfLongs(); + default: + throw new IllegalArgumentException(toBuckets.toString()); + } + } else { // objectValues + switch(toBuckets) { + case MULTISETS: + return new EclipseCollectionsMultiLookup.FromLongs.ToMultisets.OfObjects(); + case SETS: + return new EclipseCollectionsMultiLookup.FromLongs.ToSets.OfObjects(); + default: + throw new IllegalArgumentException(toBuckets.toString()); + } + } + } else { // objectKeys + if (longValues) { // V == java.lang.Long + switch(toBuckets) { + case MULTISETS: + return new EclipseCollectionsMultiLookup.FromObjects.ToMultisets.OfLongs(); + case SETS: + return new EclipseCollectionsMultiLookup.FromObjects.ToSets.OfLongs(); + default: + throw new IllegalArgumentException(toBuckets.toString()); + } + } else { // objectValues + switch(toBuckets) { + case MULTISETS: + return new EclipseCollectionsMultiLookup.FromObjects.ToMultisets.OfObjects(); + case SETS: + return new EclipseCollectionsMultiLookup.FromObjects.ToSets.OfObjects(); + default: + throw new IllegalArgumentException(toBuckets.toString()); + } + } + } + } + + @Override + @SuppressWarnings("unchecked") + public IMemory createMemory(Class values, MemoryType memoryType) { + if (Long.class.equals(values)) { // T == java.lang.Long + switch(memoryType) { + case MULTISETS: + return (IMemory) new EclipseCollectionsLongMultiset(); + case SETS: + return (IMemory) new EclipseCollectionsLongSetMemory(); + default: + throw new IllegalArgumentException(memoryType.toString()); + } + } else { // objectValues + switch(memoryType) { + case MULTISETS: + return new EclipseCollectionsMultiset<>(); + case SETS: + return new EclipseCollectionsSetMemory<>(); + default: + throw new IllegalArgumentException(memoryType.toString()); + } + } + } + + + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsLongMultiset.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsLongMultiset.java new file mode 100644 index 00000000..88773c5d --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsLongMultiset.java @@ -0,0 +1,150 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util; + +import java.util.Iterator; +import java.util.Set; +import java.util.function.BiConsumer; + +import org.eclipse.collections.impl.map.mutable.primitive.LongIntHashMap; + +/** + * @author Gabor Bergmann + * @since 2.0 + *

    TODO refactor common methods with {@link EclipseCollectionsMultiset} + *

    TODO refactor into LongBagMemory etc. + */ +public class EclipseCollectionsLongMultiset extends LongIntHashMap implements IMultiset { + + @Override + public boolean addOne(Long value) { + int oldCount = super.getIfAbsent(value, 0); + + super.put(value, oldCount + 1); + + return oldCount == 0; + } + + @Override + public boolean addSigned(Long value, int count) { + int oldCount = super.getIfAbsent(value, 0); + int newCount = oldCount + count; + + boolean becomesZero = newCount == 0; + if (newCount < 0) + throw new IllegalStateException(String.format( + "Cannot remove %d occurrences of value '%s' as only %d would remain in %s", + count, value, newCount, this)); + else if (becomesZero) + super.removeKey(value); + else // (newCount > 0) + super.put(value, newCount); + + return becomesZero || oldCount == 0; + } + + @Override + public boolean removeOne(Long value) { + return removeOneInternal(value, true); + } + /** + * @since 2.3 + */ + @Override + public boolean removeOneOrNop(Long value) { + return removeOneInternal(value, false); + } + + + /** + * @since 2.3 + */ + protected boolean removeOneInternal(Long value, boolean throwIfImpossible) { + int oldCount = super.getIfAbsent(value, 0); + if (oldCount == 0) { + if (throwIfImpossible) throw new IllegalStateException(String.format( + "Cannot remove value '%s' that is not contained in %s", + value, this)); + else return false; + } + + int rest = oldCount - 1; + boolean empty = rest == 0; + + if (!empty) { + super.put(value, rest); + } else { + super.remove(value); + } + + return empty; + } + + @Override + public void clearAllOf(Long value) { + super.remove(value); + } + + @Override + public int getCount(Long value) { + return super.getIfAbsent(value, 0); + } + @Override + public int getCountUnsafe(Object value) { + return value instanceof Long ? getCount((Long) value) : 0; + } + + @Override + public boolean containsNonZero(Long value) { + return super.containsKey(value); + } + + @Override + public boolean containsNonZeroUnsafe(Object value) { + return value instanceof Long && containsNonZero((Long) value); + } + + @Override + public Iterator iterator() { + return EclipseCollectionsLongSetMemory.iteratorOf(super.keySet()); + } + + @Override + public boolean addPositive(Long value, int count) { + if (count < 0) { + throw new IllegalArgumentException("The count value must be positive!"); + } + + int oldCount = super.getIfAbsent(value, 0); + + super.put(value, oldCount + count); + + return oldCount == 0; + } + + @Override + public Set distinctValues() { + return new EclipseCollectionsLongSetMemory.SetWrapper(super.keySet()); + } + + @Override + public void forEachEntryWithMultiplicities(BiConsumer entryConsumer) { + super.forEachKeyValue(entryConsumer::accept); + } + + @Override + public int hashCode() { + return IMemoryView.hashCode(this); + } + @Override + public boolean equals(Object obj) { + return IMemoryView.equals(this, obj); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsLongSetMemory.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsLongSetMemory.java new file mode 100644 index 00000000..fceb54fc --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsLongSetMemory.java @@ -0,0 +1,212 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; + +import org.eclipse.collections.api.LongIterable; +import org.eclipse.collections.api.iterator.LongIterator; +import org.eclipse.collections.api.set.primitive.LongSet; +import org.eclipse.collections.impl.set.mutable.primitive.LongHashSet; + +/** + * @author Gabor Bergmann + * @since 2.0 + */ +public class EclipseCollectionsLongSetMemory extends LongHashSet implements ISetMemory { + + @Override + public boolean addOne(Long value) { + return super.add(value); + } + + @Override + public boolean addSigned(Long value, int count) { + if (count == 1) return addOne(value); + else if (count == -1) return removeOne(value); + else throw new IllegalStateException(); + } + + @Override + public boolean removeOne(Long value) { + // Kept for binary compatibility + return ISetMemory.super.removeOne(value); + } + + /** + * @since 2.3 + */ + @Override + public boolean removeOneOrNop(Long value) { + return super.remove(value); + } + + @Override + public void clearAllOf(Long value) { + super.remove(value); + } + + @Override + public int getCount(Long value) { + return super.contains(value) ? 1 : 0; + } + + @Override + public int getCountUnsafe(Object value) { + return value instanceof Long ? getCount((Long) value) : 0; + } + + @Override + public boolean containsNonZero(Long value) { + return super.contains(value); + } + + @Override + public boolean containsNonZeroUnsafe(Object value) { + return value instanceof Long && containsNonZero((Long) value); + } + + @Override + public Iterator iterator() { + return iteratorOf(this); + } + + @Override + public Set distinctValues() { + return new SetWrapper(this); + } + + @Override + public boolean isEmpty() { + return super.isEmpty(); + } + + /** + * Helper for iterating a LongIterable + */ + public static Iterator iteratorOf(LongIterable wrapped) { + return new Iterator() { + LongIterator longIterator = wrapped.longIterator(); + + @Override + public boolean hasNext() { + return longIterator.hasNext(); + } + + @Override + public Long next() { + return longIterator.next(); + } + }; + } + + @Override + public int hashCode() { + return IMemoryView.hashCode(this); + } + @Override + public boolean equals(Object obj) { + return IMemoryView.equals(this, obj); + } + + + /** + * Helper that presents a primitive collection as a Set view + * @author Gabor Bergmann + */ + public static final class SetWrapper implements Set { + private LongSet wrapped; + + /** + * @param wrapped + */ + public SetWrapper(LongSet wrapped) { + this.wrapped = wrapped; + } + + @Override + public int size() { + return wrapped.size(); + } + + @Override + public boolean isEmpty() { + return wrapped.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return o instanceof Long && wrapped.contains((Long)o); + } + + @Override + public Iterator iterator() { + return iteratorOf(wrapped); + } + + @Override + public boolean containsAll(Collection c) { + for (Object object : c) { + if (contains(object)) + return true; + } + return false; + } + + @Override + public Object[] toArray() { + return toArray(new Long[wrapped.size()]); + } + + @Override + @SuppressWarnings("unchecked") + public T[] toArray(T[] a) { + int k = 0; + LongIterator iterator = wrapped.longIterator(); + while (iterator.hasNext()) + a[k++] = (T) Long.valueOf(iterator.next()); + return a; + } + + @Override + public boolean add(Long e) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(Object o) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsMultiLookup.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsMultiLookup.java new file mode 100644 index 00000000..394135c9 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsMultiLookup.java @@ -0,0 +1,226 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util; + +import org.eclipse.collections.impl.map.mutable.UnifiedMap; +import org.eclipse.collections.impl.map.mutable.primitive.LongObjectHashMap; +import tools.refinery.viatra.runtime.matchers.util.MarkedMemory.MarkedMultiset; +import tools.refinery.viatra.runtime.matchers.util.MarkedMemory.MarkedSet; + +import java.util.Set; +import java.util.stream.Stream; + + + +/** + * Eclipse Collections-based realizations of {@link IMultiLookup} + * + * @author Gabor Bergmann + * @since 2.0 + */ +class EclipseCollectionsMultiLookup { + + private EclipseCollectionsMultiLookup() {/* Hidden utility class constructor */} + + private static class MarkedSetImpl extends EclipseCollectionsSetMemory implements MarkedMemory.MarkedSet {} + private static class MarkedMultisetImpl extends EclipseCollectionsMultiset implements MarkedMemory.MarkedMultiset {} + private static class MarkedLongSetImpl extends EclipseCollectionsLongSetMemory implements MarkedMemory.MarkedSet {} + private static class MarkedLongMultisetImpl extends EclipseCollectionsLongMultiset implements MarkedMemory.MarkedMultiset {} + + public abstract static class FromObjects> + extends UnifiedMap implements IMultiLookupAbstract { + + @Override + public boolean equals(Object obj) { + return IMultiLookup.equals(this, obj); + } + @Override + public int hashCode() { + return IMultiLookup.hashCode(this); + } + + + @Override + public Object lowLevelPutIfAbsent(Key key, Value value) { + return super.putIfAbsent(key, value); + } + + @Override + public Object lowLevelGet(Key key) { + return super.get(key); + } + + @Override + public Object lowLevelGetUnsafe(Object key) { + return super.get(key); + } + + @Override + public Object lowLevelRemove(Key key) { + return super.remove(key); + } + + @Override + public void lowLevelPut(Key key, Object valueOrBucket) { + super.put(key, valueOrBucket); + } + @Override + public Iterable lowLevelValues() { + return super.values(); + } + @Override + public Set lowLevelKeySet() { + return super.keySet(); + } + @Override + public int lowLevelSize() { + return super.size(); + } + + @Override + public Stream distinctKeysStream() { + // may be more efficient than the default spliterator + return super.keySet().stream(); + } + + public abstract static class ToSets extends FromObjects> + implements IMultiLookupAbstract.ToSetsAbstract + { + public static class OfObjects extends ToSets { + @Override + public MarkedSet createMarkedSet() { + return new MarkedSetImpl(); + } + } + + public static class OfLongs extends ToSets { + @Override + public MarkedSet createMarkedSet() { + return new MarkedLongSetImpl(); + } + } + + } + + public abstract static class ToMultisets extends FromObjects> + implements IMultiLookupAbstract.ToMultisetsAbstract + { + public static class OfObjects extends ToMultisets { + @Override + public MarkedMultiset createMarkedMultiset() { + return new MarkedMultisetImpl(); + } + } + + public static class OfLongs extends ToMultisets { + @Override + public MarkedMultiset createMarkedMultiset() { + return new MarkedLongMultisetImpl(); + } + } + + } + + } + + public abstract static class FromLongs> + extends LongObjectHashMap implements IMultiLookupAbstract { + + @Override + public boolean equals(Object obj) { + return IMultiLookup.equals(this, obj); + } + @Override + public int hashCode() { + return IMultiLookup.hashCode(this); + } + + @Override + public Object lowLevelPutIfAbsent(Long key, Value value) { + Object old = super.get(key); + if (old == null) super.put(key, value); + return old; + } + + @Override + public Object lowLevelGet(Long key) { + return super.get(key); + } + + @Override + public Object lowLevelGetUnsafe(Object key) { + return key instanceof Long ? super.get((Long)key) : null; + } + + @Override + public Object lowLevelRemove(Long key) { + return super.remove(key); + } + + @Override + public void lowLevelPut(Long key, Object valueOrBucket) { + super.put(key, valueOrBucket); + } + @Override + public Iterable lowLevelValues() { + return super.values(); + } + @Override + public int lowLevelSize() { + return super.size(); + } + @Override + public Iterable lowLevelKeySet() { + return () -> EclipseCollectionsLongSetMemory.iteratorOf(FromLongs.super.keysView()); + } + + public abstract static class ToSets extends FromLongs> + implements IMultiLookupAbstract.ToSetsAbstract + { + public static class OfObjects extends ToSets { + @Override + public MarkedSet createMarkedSet() { + return new MarkedSetImpl(); + } + } + + public static class OfLongs extends ToSets { + @Override + public MarkedSet createMarkedSet() { + return new MarkedLongSetImpl(); + } + } + + } + + public abstract static class ToMultisets extends FromLongs> + implements IMultiLookupAbstract.ToMultisetsAbstract + { + public static class OfObjects extends ToMultisets { + @Override + public MarkedMultiset createMarkedMultiset() { + return new MarkedMultisetImpl(); + } + } + + public static class OfLongs extends ToMultisets { + @Override + public MarkedMultiset createMarkedMultiset() { + return new MarkedLongMultisetImpl(); + } + } + + } + + } + + +} + + diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsMultiset.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsMultiset.java new file mode 100644 index 00000000..46977c8b --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsMultiset.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util; + +/** + * @author Gabor Bergmann + * @since 1.7 + */ +public class EclipseCollectionsMultiset extends EclipseCollectionsBagMemory implements IMultiset { + + @Override + public boolean addOne(T value) { + int oldCount = super.getIfAbsent(value, 0); + + super.put(value, oldCount + 1); + + return oldCount == 0; + } + + @Override + public boolean addPositive(T value, int count) { + if (count < 0) { + throw new IllegalArgumentException("The count value must be positive!"); + } + + int oldCount = super.getIfAbsent(value, 0); + + super.put(value, oldCount + count); + + return oldCount == 0; + } + + @Override + public boolean addSigned(T value, int count) { + int oldCount = super.getIfAbsent(value, 0); + int newCount = oldCount + count; + + boolean becomesZero = newCount == 0; + if (newCount < 0) + throw new IllegalStateException(String.format( + "Cannot remove %d occurrences of value '%s' as only %d would remain in %s", + count, value, newCount, this)); + else if (becomesZero) + super.removeKey(value); + else // (newCount > 0) + super.put(value, newCount); + + return becomesZero || oldCount == 0; + } + + + @Override + public boolean removeOne(T value) { + return removeOneInternal(value, true); + } + + @Override + public boolean removeOneOrNop(T value) { + return removeOneInternal(value, false); + } + + /** + * @since 2.3 + */ + protected boolean removeOneInternal(T value, boolean throwIfImpossible) { + int oldCount = super.getIfAbsent(value, 0); + if (oldCount == 0) { + if (throwIfImpossible) throw new IllegalStateException(String.format( + "Cannot remove value '%s' that is not contained in %s", + value, this)); + else return false; + } + + int rest = oldCount - 1; + boolean empty = rest == 0; + + if (!empty) { + super.put(value, rest); + } else { + super.remove(value); + } + + return empty; + } + + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsSetMemory.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsSetMemory.java new file mode 100644 index 00000000..92f65246 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/EclipseCollectionsSetMemory.java @@ -0,0 +1,94 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util; + +import java.util.Set; + +import org.eclipse.collections.impl.set.mutable.UnifiedSet; + +/** + * @author Gabor Bergmann + * @since 2.0 + */ +public class EclipseCollectionsSetMemory extends UnifiedSet implements ISetMemory { + @Override + public int getCount(Value value) { + return super.contains(value) ? 1 : 0; + } + @Override + public int getCountUnsafe(Object value) { + return super.contains(value) ? 1 : 0; + } + @Override + public boolean containsNonZero(Value value) { + return super.contains(value); + } + + @Override + public boolean containsNonZeroUnsafe(Object value) { + return super.contains(value); + } + + @Override + public boolean addOne(Value value) { + return super.add(value); + } + + @Override + public boolean addSigned(Value value, int count) { + if (count == 1) return addOne(value); + else if (count == -1) return removeOne(value); + else throw new IllegalStateException(); + } + + @Override + public boolean removeOne(Value value) { + // Kept for binary compatibility + return ISetMemory.super.removeOne(value); + } + + @Override + public boolean removeOneOrNop(Value value) { + return super.remove(value); + } + + @Override + public void clearAllOf(Value value) { + super.remove(value); + } + + @Override + public Set distinctValues() { + return this; + } + + @Override + public Value theContainedVersionOf(Value value) { + return super.get(value); + } + + @Override + @SuppressWarnings("unchecked") + public Value theContainedVersionOfUnsafe(Object value) { + if (super.contains(value)) + return super.get((Value)value); + else return null; + } + + @Override + public int hashCode() { + return IMemoryView.hashCode(this); + } + @Override + public boolean equals(Object obj) { + return IMemoryView.equals(this, obj); + } + + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/EmptyMemory.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/EmptyMemory.java new file mode 100644 index 00000000..a17b3a3f --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/EmptyMemory.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util; + +import java.util.Collections; +import java.util.Iterator; +import java.util.Set; + +/** + * A singleton immutable empty memory. + * @author Gabor Bergmann + * @since 2.0 + * + */ +public class EmptyMemory implements IMemoryView { + + @SuppressWarnings("rawtypes") + private static final EmptyMemory INSTANCE = new EmptyMemory(); + + @SuppressWarnings("unchecked") + public static EmptyMemory instance() { + return INSTANCE; + } + + + + /** + * Singleton; hidden constructor + */ + private EmptyMemory() { + super(); + } + + @Override + public Iterator iterator() { + return Collections.emptySet().iterator(); + } + + @Override + public int getCount(T value) { + return 0; + } + + @Override + public int getCountUnsafe(Object value) { + return 0; + } + + @Override + public boolean containsNonZero(T value) { + return false; + } + + @Override + public boolean containsNonZeroUnsafe(Object value) { + return false; + } + + @Override + public int size() { + return 0; + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public Set distinctValues() { + return Collections.emptySet(); + } + + @Override + public int hashCode() { + return IMemoryView.hashCode(this); + } + @Override + public boolean equals(Object obj) { + return IMemoryView.equals(this, obj); + } + + @Override + public String toString() { + return "{}"; + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/ICache.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/ICache.java new file mode 100644 index 00000000..8c2e54ad --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/ICache.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util; + +import java.util.function.Supplier; + +/** + * A cache is a simple key-value pair that stores calculated values for specific key objects + * + *

    + * NOTE These caches are not expected to be used outside query backend implementations + * + * @author Zoltan Ujhelyi + * @since 1.7 + * @noreference This interface is not intended to be referenced by clients. + */ +public interface ICache { + + /** + * Return a selected value for the key object. If the value is not available in the cache yet, the given provider is + * called once + * @since 2.0 + */ + T getValue(Object key, Class clazz, Supplier valueProvider); + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/IDeltaBag.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/IDeltaBag.java new file mode 100644 index 00000000..99a4cb3b --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/IDeltaBag.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util; + +/** + * An {@link IMemory} that represents the difference between two states of a set or {@link IMultiset}, and therefore + * may contain values with a negative multiplicity. + * + * @author Gabor Bergmann + * @since 1.7 + */ +public interface IDeltaBag extends IMemory { + + @Override + default boolean removeOneOrNop(T value) { + // makes no difference for delta bags + return removeOne(value); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMemory.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMemory.java new file mode 100644 index 00000000..ea788e53 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMemory.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util; + +/** + * A memory containing a positive or negative number of equal() copies for some values. + * During iterations, each distinct value is iterated only once. + * + *

    Refined by:

      + *
    • {@link IMultiset}, which always contains values with a nonnegative multiplicity.
    • + *
    • {@link IDeltaBag}, which may contain values with negative multiplicity.
    • + *
    • {@link ISetMemory}, which is just a set (allowed multiplicities: 0 and 1).
    • + *
    + * + * @author Gabor Bergmann + * @since 1.7 + * @noimplement This interface is not intended to be implemented by clients. + */ +public interface IMemory extends IMemoryView, Clearable { + + /** + * Adds one value occurrence to the memory. + * + * @return true if the tuple was not present before in the memory, or + * (in case of {@link IDeltaBag}) is no longer present in the memory + */ + boolean addOne(T value); + + /** + * Adds the given number of occurrences to the memory. The count value may or may not be negative. + *

    Precondition if {@link IMultiset}: at least the given amount of occurrences exist, if count is negative. + *

    Precondition if {@link ISetMemory}: count is +1 or -1, the latter is only allowed if the set contains the value. + * + * @param count + * the number of occurrences + * @return true if the tuple was not present before in the memory, or is no longer present in the memory + * @throws IllegalStateException if {@link IMultiset} or {@link ISetMemory} and the number of occurrences in the memory would underflow to negative + */ + boolean addSigned(T value, int count); + + /** + * Removes one occurrence of the given value from the memory. + *

    Precondition if {@link IMultiset} or {@link ISetMemory}: the value must have a positive amount of occurrences in the memory. + * + * @return true if this was the the last occurrence of the value, or + * (in case of {@link IDeltaBag}) is the first negative occurrence of the value + * @throws IllegalStateException if {@link IMultiset} or {@link ISetMemory} and value had no occurrences in the memory + */ + boolean removeOne(T value); + + /** + * Removes one occurrence of the given value from the memory, if possible. + * + *

    Memory is unchanged and false is returned if + * {@link IMultiset} or {@link ISetMemory} and value had no occurrences in the memory + * + * @return true if this was the the last occurrence of the value, or + * (in case of {@link IDeltaBag}) is the first negative occurrence of the value + * + * @since 2.3 + */ + boolean removeOneOrNop(T value); + + /** + * Removes all occurrences of the given value from the memory. + */ + void clearAllOf(T value); + + /** + * Empties out the memory. + */ + @Override + void clear(); + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMemoryView.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMemoryView.java new file mode 100644 index 00000000..add575c6 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMemoryView.java @@ -0,0 +1,205 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util; + +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +/** + * A read-only view on a memory containing a positive or negative number of equal() copies for some values. + * During iterations, each distinct value is iterated only once. + * + *

    See {@link IMemory}. + * + *

    Implementors must provide semantic (not identity-based) hashCode() and equals() using the static helpers {@link #hashCode(IMemoryView)} and {@link #equals(IMemoryView, Object)} here. + * + * @author Gabor Bergmann + * + * @since 2.0 + */ +public interface IMemoryView extends Iterable { + + /** + * Returns the number of occurrences of the given value. + * + * @return the number of occurrences + */ + int getCount(T value); + + /** + * Returns the number of occurrences of the given value (which may be of any type). + * + * @return the number of occurrences + */ + int getCountUnsafe(Object value); + + /** + * @return true if the given value is contained with a nonzero multiplicity + */ + boolean containsNonZero(T value); + + /** + * @return true if the given value (which may be of any type) is contained with a nonzero multiplicity + */ + boolean containsNonZeroUnsafe(Object value); + + /** + * @return the number of distinct values + */ + int size(); + + /** + * + * @return iff contains at least one value with non-zero occurrences + */ + boolean isEmpty(); + + /** + * The set of distinct values + */ + Set distinctValues(); + + + /** + * Where supported, returns the stored element that is equal to the given value, or null if none. + * Useful for canonicalization in case of non-identity equals(). + * + *

    For collections that do not support canonicalization, simply returns the argument if contained, null if none. + * + * @return a value equal to the argument if such a value is stored, or null if none + */ + default T theContainedVersionOf(T value) { + if (containsNonZero(value)) return value; else return null; + } + + /** + * Where supported, returns the stored element that is equal to the given value (of any type), + * or null if none. + * Useful for canonicalization in case of non-identity equals(). + * + *

    For collections that do not support canonicalization, simply returns the argument if contained, null if none. + * + * @return a value equal to the argument if such a value is stored, or null if none + */ + @SuppressWarnings("unchecked") + default T theContainedVersionOfUnsafe(Object value) { + if (containsNonZeroUnsafe(value)) return (T) value; else return null; + } + + + /** + * @return an unmodifiable view of contained values with their multiplicities + */ + default Iterable> entriesWithMultiplicities() { + return () -> { + Iterator wrapped = distinctValues().iterator(); + return new Iterator> () { + @Override + public boolean hasNext() { + return wrapped.hasNext(); + } + + @Override + public Map.Entry next() { + T key = wrapped.next(); + int count = getCount(key); + return new Map.Entry(){ + @Override + public T getKey() { + return key; + } + + @Override + public Integer getValue() { + return count; + } + + @Override + public Integer setValue(Integer value) { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + return String.format("%d of %s", count, key); + } + + }; + } + + }; + }; + } + + /** + * Process contained values with their multiplicities + */ + default void forEachEntryWithMultiplicities(BiConsumer entryConsumer) { + for (T value : distinctValues()) { + entryConsumer.accept(value, getCount(value)); + } + } + + + /** + * For compatibility with legacy code relying on element-to-integer maps. + * @return an unmodifiable view of contained values with their multiplicities + */ + public default Map asMap() { + return new MemoryViewBackedMapView<>(this); + } + + /** + * For compatibility with legacy code relying on element-to-integer maps. + * @return an unmodifiable view of contained values with their multiplicities + */ + public static IMemoryView fromMap(Map wrapped) { + return new MapBackedMemoryView<>(wrapped); + } + + /** + * @return a stream of values, iterable once + * @since 2.1 + */ + public default Stream asStream() { + return StreamSupport.stream(spliterator(), false); + } + + /** + * Provides semantic equality comparison. + */ + public static boolean equals(IMemoryView self, Object obj) { + if (obj instanceof IMemoryView) { + IMemoryView other = (IMemoryView) obj; + if (other.size() != self.size()) return false; + for (Entry entry : other.entriesWithMultiplicities()) { + if ( !entry.getValue().equals(self.getCountUnsafe(entry.getKey()))) + return false; + } + return true; + } + return false; + } + + /** + * Provides semantic hashCode() comparison. + */ + public static int hashCode(IMemoryView memory) { + int hashCode = 0; + for (T value : memory.distinctValues()) { + hashCode += value.hashCode() ^ Integer.hashCode(memory.getCount(value)); + } + return hashCode; + } +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMultiLookup.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMultiLookup.java new file mode 100644 index 00000000..1ce1d2c9 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMultiLookup.java @@ -0,0 +1,216 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util; + +import java.util.stream.Stream; + +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType; + +/** + * A multi-map that associates sets / multisets / delta sets of values to each key. + * + *

    Implementors must provide semantic (not identity-based) hashCode() and equals() using the static helpers {@link #hashCode(IMultiLookup)} and {@link #equals(IMultiLookup, Object)} here. + * + * @author Gabor Bergmann + * @since 2.0 + * @noimplement This interface is not intended to be implemented by clients. + */ +public interface IMultiLookup { + + /** + * Returns true if this collection is empty, false otherwise. + * @since 2.2 + */ + boolean isEmpty(); + + + /** + * Returns true if there are any values associated with the given key. + * @param key a key for which associated values are sought + * @since 2.3 + */ + boolean lookupExists(Key key); + + /** + * Returns a (read-only) bucket of values associated with the given key. + * Clients must not modify the returned bucket. + * @param key a key for which associated values are sought + * @return null if key not found, a bucket of values otherwise + */ + IMemoryView lookup(Key key); + + /** + * Returns a (read-only) bucket of values associated with the given key. + * Clients must not modify the returned bucket. + * @param key a key for which associated values are sought + * @return a bucket of values, never null + */ + default IMemoryView lookupOrEmpty(Key key) { + IMemoryView bucket = lookup(key); + return bucket == null ? EmptyMemory.instance() : bucket; + } + + /** + * Returns a (read-only) bucket of values associated with the given key, while simultaneously removing them. + * Clients must not modify the returned bucket. + * @param key a key for which associated values are sought + * @return a bucket of values, never null + * @since 2.3 + */ + IMemoryView lookupAndRemoveAll(Key key); + + /** + * Returns a (read-only) bucket of values associated with the given key, which can be of any type. + * Clients must not modify the returned bucket. + * @param key a key for which associated values are sought (may or may not be of Key type) + * @return null if key not found, a bucket of values otherwise + */ + IMemoryView lookupUnsafe(Object key); + + /** + * Returns a (read-only) bucket of values associated with the given key. + * Clients must not modify the returned bucket. + * @param key a key for which associated values are sought (may or may not be of Key type) + * @return a bucket of values, never null + */ + default IMemoryView lookupUnsafeOrEmpty(Object key) { + IMemoryView bucket = lookupUnsafe(key); + return bucket == null ? EmptyMemory.instance() : bucket; + } + + + + /** + * @return the set of distinct keys that have values associated. + */ + Iterable distinctKeys(); + + /** + * @return the set of distinct keys that have values associated. + * @since 2.3 + */ + Stream distinctKeysStream(); + + /** + * @return the number of distinct keys that have values associated. + */ + int countKeys(); + + /** + * Iterates once over each distinct value. + */ + Iterable distinctValues(); + + /** + * Iterates once over each distinct value. + * @since 2.3 + */ + Stream distinctValuesStream(); + + + + /** + * How significant was the change? * + * @author Gabor Bergmann + */ + public enum ChangeGranularity { + /** + * First key-value pair with given key inserted, or last pair with given key deleted. + * (In case of delta maps, also if last negative key-value pair with given key neutralized.) + */ + KEY, + /** + * First occurrence of given key-value pair inserted, or last occurrence of the pair deleted, while key still has values associated. + * (In case of delta maps, also if last negative occurrence of key-value pair neutralized.) + */ + VALUE, + /** + * Duplicate key-value pair inserted or deleted. + */ + DUPLICATE + } + + /** + * Adds key-value pair to the lookup structure, or fails if not possible. + *

    If the addition would cause duplicates but the bucket type does not allow it ({@link MemoryType#SETS}), + * the operation throws an {@link IllegalStateException}. + * @return the granularity of the change + * @throws IllegalStateException if addition would cause duplication that is not permitted + */ + public ChangeGranularity addPair(Key key, Value value); + /** + * Adds key-value pair to the lookup structure. + *

    If the addition would cause duplicates but the bucket type does not allow it ({@link MemoryType#SETS}), + * the operation is silently ignored and {@link ChangeGranularity#DUPLICATE} is returned. + * @return the granularity of the change, or {@link ChangeGranularity#DUPLICATE} if addition would result in a duplicate and therefore ignored + * @since 2.3 + */ + public ChangeGranularity addPairOrNop(Key key, Value value); + /** + * Removes key-value pair from the lookup structure, or fails if not possible. + *

    When attempting to remove a key-value pair with zero multiplicity from a non-delta bucket type + * ({@link MemoryType#SETS} or {@link MemoryType#MULTISETS}}), an {@link IllegalStateException} is thrown. + * @return the granularity of the change + * @throws IllegalStateException if removing non-existing element that is not permitted + */ + public ChangeGranularity removePair(Key key, Value value); + /** + * Removes key-value pair from the lookup structure. + *

    When attempting to remove a key-value pair with zero multiplicity from a non-delta bucket type + * ({@link MemoryType#SETS} or {@link MemoryType#MULTISETS}}), + * the operation is silently ignored and {@link ChangeGranularity#DUPLICATE} is returned. + * @return the granularity of the change + * @throws IllegalStateException if removing non-existing element that is not permitted + * @since 2.3 + */ + public ChangeGranularity removePairOrNop(Key key, Value value); + + /** + * Updates multiplicity of key-value pair by a positive amount. + * + *

    PRE: count > 0 + * + * @return the granularity of the change + * @throws IllegalStateException if addition would cause duplication that is not permitted + */ + public ChangeGranularity addPairPositiveMultiplicity(Key key, Value value, int count); + + /** + * Empties out the lookup structure. + */ + public void clear(); + + /** + * Provides semantic equality comparison. + */ + public static boolean equals(IMultiLookup self, Object obj) { + if (obj instanceof IMultiLookup) { + IMultiLookup other = (IMultiLookup) obj; + if (other.countKeys() != self.countKeys()) return false; + for (Object key : other.distinctKeys()) { + if (! other.lookupUnsafe(key).equals(self.lookupUnsafe(key))) + return false; + } + return true; + } + return false; + } + + /** + * Provides semantic hashCode() comparison. + */ + public static int hashCode(IMultiLookup memory) { + int hashCode = 0; + for (Key key : memory.distinctKeys()) { + hashCode += key.hashCode() ^ memory.lookup(key).hashCode(); + } + return hashCode; + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMultiLookupAbstract.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMultiLookupAbstract.java new file mode 100644 index 00000000..8b1944c1 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMultiLookupAbstract.java @@ -0,0 +1,485 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util; + +import java.util.Collections; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import tools.refinery.viatra.runtime.matchers.util.MarkedMemory.MarkedSet; + +/** + * Specialized multimap implementation that saves memory + * by storing singleton value objects (multiplicity 1) instead of multiset buckets + * whenever there is only one value associated with a key. + * + *

    See specialized {@link ToSetsAbstract}, {@link ToMultisetsAbstract} for various bucket types. + * + *

    Implemented as a Key->Object map with invariant:

      + *
    • key maps to null if associated with no values; + *
    • key maps to a single Value iff it is associated with a single value of multiplicity +1; + *
    • key maps to Bucket otherwise + *
    + * + * Note that due to the above invariant, handling +1 and -1 are asymmetric in case of delta maps. + * + *

    Not intended as an API, but rather as a 'base class' for implementors. + * Realized as an interface with default implementations, instead of an abstract class, + * to ensure that implementors can easily choose a base class such as UnifiedMap to augment. + * + *

    Implementor should inherit from a Map-like class (primitive map possible) + * and bind the lowLevel* methods accordingly. + * + * @noreference This interface is not intended to be referenced by clients. + * @noimplement This interface is not intended to be implemented by clients. + * + * @author Gabor Bergmann + * @since 2.0 + * + * + */ +public interface IMultiLookupAbstract> extends IMultiLookup { + + // the following methods must be bound to a concrete Map-like structure (primitive implementation allowed) + + /** + * Implementor shall bind to the low-level get() or equivalent of the underlying Key-to-Object map + */ + abstract Object lowLevelGet(Key key); + + /** + * Implementor shall bind to the low-level get() or equivalent of the underlying Key-to-Object map + */ + abstract Object lowLevelGetUnsafe(Object key); + + /** + * Implementor shall bind to the low-level remove() or equivalent of the underlying Key-to-Object map + */ + abstract Object lowLevelRemove(Key key); + + /** + * Implementor shall bind to the low-level putIfAbsent() or equivalent of the underlying Key-to-Object map + */ + abstract Object lowLevelPutIfAbsent(Key key, Value value); + + /** + * Implementor shall bind to the low-level put() or equivalent of the underlying Key-to-Object map + */ + abstract void lowLevelPut(Key key, Object valueOrBucket); + + /** + * Implementor shall bind to the low-level values() or equivalent of the underlying Key-to-Object map + */ + abstract Iterable lowLevelValues(); + + /** + * Implementor shall bind to the low-level keySet() or equivalent of the underlying Key-to-Object map + */ + abstract Iterable lowLevelKeySet(); + + /** + * Implementor shall bind to the low-level size() or equivalent of the underlying Key-to-Object map + */ + abstract int lowLevelSize(); + + + // generic multi-lookup logic + + @Override + default boolean lookupExists(Key key) { + Object object = lowLevelGet(key); + return null != object; + } + + @Override + public default IMemoryView lookup(Key key) { + Object object = lowLevelGet(key); + if (object == null) return null; + if (object instanceof MarkedMemory) return (Bucket) object; + return yieldSingleton((Value)object); + } + + @Override + default IMemoryView lookupAndRemoveAll(Key key) { + Object object = lowLevelRemove(key); + if (object == null) return EmptyMemory.instance(); + if (object instanceof MarkedMemory) return (Bucket) object; + return yieldSingleton((Value)object); + } + + @Override + public default IMemoryView lookupUnsafe(Object key) { + Object object = lowLevelGetUnsafe(key); + if (object == null) return null; + if (object instanceof MarkedMemory) return (Bucket) object; + return yieldSingleton((Value)object); + } + + @Override + public default ChangeGranularity addPair(Key key, Value value) { + return addPairInternal(key, value, true); + } + + @Override + default ChangeGranularity addPairOrNop(Key key, Value value) { + return addPairInternal(key, value, false); + } + + public default ChangeGranularity addPairInternal(Key key, Value value, boolean throwIfImpossible) { + Object old = lowLevelPutIfAbsent(key, value); + boolean keyChange = (old == null); + + if (keyChange) { // key was not present + return ChangeGranularity.KEY; + } else { // key was already present + Bucket bucket; + if (old instanceof MarkedMemory) { // ... as collection + bucket = (Bucket) old; + } else { // ... as singleton + if (!this.duplicatesAllowed() && Objects.equals(value, old)) { + if (throwIfImpossible) + throw new IllegalStateException(); + else + return ChangeGranularity.DUPLICATE; + } + bucket = createSingletonBucket((Value) old); + lowLevelPut(key, bucket); + } + // will throw if forbidden duplicate, return false if allowed duplicate + if (addToBucket(bucket, value, throwIfImpossible)) { + // deltas may become empty or a singleton after addition! + if (negativesAllowed()) { + if (bucket.isEmpty()) { + lowLevelRemove(key); + return ChangeGranularity.KEY; + } else { + handleSingleton(key, bucket); + return ChangeGranularity.VALUE; + } + } else return ChangeGranularity.VALUE; + } else return ChangeGranularity.DUPLICATE; + } + } + + @Override + // TODO deltas not supproted yet + default ChangeGranularity addPairPositiveMultiplicity(Key key, Value value, int count) { + if (count == 1) return addPair(key, value); + // count > 1, always end up with non-singleton bucket + + Object old = lowLevelGet(key); + boolean keyChange = (old == null); + + Bucket bucket; + if (keyChange) { // ... nothing associated to key yet + bucket = createSingletonBucket(value); + lowLevelPut(key, bucket); + --count; // one less to increment later + } else if (old instanceof MarkedMemory) { // ... as collection + bucket = (Bucket) old; + } else { // ... as singleton + bucket = createSingletonBucket((Value) old); + lowLevelPut(key, bucket); + } + + boolean newValue = bucket.addSigned(value, count); + + if (keyChange) return ChangeGranularity.KEY; + else if (newValue) return ChangeGranularity.VALUE; + else return ChangeGranularity.DUPLICATE; + } + + @Override + public default ChangeGranularity removePair(Key key, Value value) { + return removePairInternal(key, value, true); + } + + @Override + default ChangeGranularity removePairOrNop(Key key, Value value) { + return removePairInternal(key, value, false); + } + + public default ChangeGranularity removePairInternal(Key key, Value value, boolean throwIfImpossible) { + Object old = lowLevelGet(key); + if (old instanceof MarkedMemory) { // ... as collection + @SuppressWarnings("unchecked") + Bucket bucket = (Bucket) old; + // will throw if removing non-existent, return false if removing duplicate + boolean valueChange = removeFromBucket(bucket, value, throwIfImpossible); + handleSingleton(key, bucket); + if (valueChange) + return ChangeGranularity.VALUE; + else + return ChangeGranularity.DUPLICATE; + } else if (value.equals(old)) { // matching singleton + lowLevelRemove(key); + return ChangeGranularity.KEY; + } else { // different singleton, will produce a delta if possible + if (negativesAllowed()) { + Bucket deltaBucket = createDeltaBucket((Value) old, value); // will throw if no deltas supported + lowLevelPut(key, deltaBucket); + return ChangeGranularity.VALUE; // no key change + } else { + if (throwIfImpossible) + throw new IllegalStateException(); + else + return ChangeGranularity.DUPLICATE; + } + } + } + + public default void handleSingleton(Key key, Bucket bucket) { + Value remainingSingleton = asSingleton(bucket); + if (remainingSingleton != null) { // only one remains + lowLevelPut(key, remainingSingleton); + } + } + + @Override + public default Iterable distinctValues() { + return new Iterable() { + private final Iterator EMPTY_ITERATOR = Collections.emptySet().iterator(); + @Override + public Iterator iterator() { + return new Iterator() { + Iterator bucketIterator = lowLevelValues().iterator(); + Iterator elementIterator = EMPTY_ITERATOR; + + @Override + public boolean hasNext() { + return (elementIterator.hasNext() || bucketIterator.hasNext()); + } + + @Override + public Value next() { + if (elementIterator.hasNext()) + return elementIterator.next(); + else if (bucketIterator.hasNext()) { + Object bucket = bucketIterator.next(); + if (bucket instanceof MarkedMemory) { + elementIterator = + ((MarkedMemory) bucket).distinctValues().iterator(); + return elementIterator.next(); + } else { + elementIterator = EMPTY_ITERATOR; + return (Value) bucket; + } + } else + throw new NoSuchElementException(); + } + + /** + * Not implemented + */ + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + }; + } + }; + } + + @Override + default Stream distinctValuesStream() { + return StreamSupport.stream(distinctValues().spliterator(), false); + } + + @Override + default Iterable distinctKeys() { + return lowLevelKeySet(); + } + + @Override + default Stream distinctKeysStream() { + return StreamSupport.stream(distinctKeys().spliterator(), false); + } + + @Override + default int countKeys() { + return lowLevelSize(); + } + + // the following methods are customized for bucket type + + /** + * @return iff negative multiplicites are allowed + */ + abstract boolean negativesAllowed(); + + /** + * @return iff larger-than-1 multiplicites are allowed + * @since 2.3 + */ + abstract boolean duplicatesAllowed(); + + /** + * Increases the multiplicity of the value in the bucket. + * @return true iff non-duplicate + * @throws IllegalStateException if disallowed duplication and throwIfImpossible is specified + */ + abstract boolean addToBucket(Bucket bucket, Value value, boolean throwIfImpossible); + + /** + * Decreases the multiplicity of the value in the bucket. + * @return false if removing duplicate value + * @throws IllegalStateException if removing non-existing value (unless delta map) and throwIfImpossible is specified + */ + abstract boolean removeFromBucket(Bucket bucket, Value value, boolean throwIfImpossible); + + /** + * Checks whether the bucket is a singleton, i.e. it contains a single value with multiplicity +1 + * @return the singleton value, or null if the bucket is not singleton + */ + abstract Value asSingleton(Bucket bucket); + + /** + * @return a new bucket consisting of a sole value + */ + abstract Bucket createSingletonBucket(Value value); + /** + * @return a read-only bucket consisting of a sole value, to be returned to the user + */ + default IMemoryView yieldSingleton(Value value) { + return new SingletonMemoryView<>(value); + } + + /** + * @param positive the previously existing value, or null if the delta is to contain a single negative tuple + * @return a new bucket consisting of a delta of two values + * @throws IllegalStateException if deltas not supported + */ + abstract Bucket createDeltaBucket(Value positive, Value negative); + + /** + * A multi-lookup whose buckets are sets. + * + *

    Not intended as an API, but rather as a 'base class' for implementors. + * Realized as an interface with default implementations, instead of an abstract class, + * to ensure that implementors can easily choose a base class such as UnifiedMap to augment. + * + *

    Implementor should inherit from a Map-like class (primitive map possible) + * and bind the lowLevel* methods accordingly. + * + * @noreference This interface is not intended to be referenced by clients. + * @noimplement This interface is not intended to be implemented by clients. + * @author Gabor Bergmann + */ + public static interface ToSetsAbstract extends IMultiLookupAbstract> { + /** + * @return a fresh, empty marked set + */ + public MarkedSet createMarkedSet(); + + @Override + public default boolean negativesAllowed() { + return false; + } + @Override + default boolean duplicatesAllowed() { + return false; + } + + @Override + public default boolean addToBucket(MarkedSet bucket, Value value, boolean throwIfImpossible) { + if (bucket.addOne(value)) return true; + else if (throwIfImpossible) throw new IllegalStateException(); + else return false; + } + + @Override + public default boolean removeFromBucket(MarkedSet bucket, Value value, boolean throwIfImpossible) { + return throwIfImpossible ? bucket.removeOne(value) : bucket.removeOneOrNop(value); + } + + @Override + public default Value asSingleton(MarkedSet bucket) { + return bucket.size() == 1 ? bucket.iterator().next() : null; + } + + @Override + public default MarkedSet createSingletonBucket(Value value) { + MarkedSet result = createMarkedSet(); + result.addOne(value); + return result; + } + + @Override + public default MarkedSet createDeltaBucket(Value positive, Value negative) { + throw new IllegalStateException(); + } + } + + /** + * A multi-lookup whose buckets are multisets. + * + *

    Not intended as an API, but rather as a 'base class' for implementors. + * Realized as an interface with default implementations, instead of an abstract class, + * to ensure that implementors can easily choose a base class such as UnifiedMap to augment. + * + *

    Implementor should inherit from a Map-like class (primitive map possible) + * and bind the lowLevel* methods accordingly. + * + * @noreference This interface is not intended to be referenced by clients. + * @noimplement This interface is not intended to be implemented by clients. + * @author Gabor Bergmann + */ + public static interface ToMultisetsAbstract extends IMultiLookupAbstract> { + /** + * @return a fresh, empty marked multiset + */ + public MarkedMemory.MarkedMultiset createMarkedMultiset(); + + @Override + public default boolean negativesAllowed() { + return false; + } + @Override + default boolean duplicatesAllowed() { + return true; + } + + @Override + public default boolean addToBucket(MarkedMemory.MarkedMultiset bucket, Value value, boolean throwIfImpossible) { + return bucket.addOne(value); + } + + @Override + public default boolean removeFromBucket(MarkedMemory.MarkedMultiset bucket, Value value, boolean throwIfImpossible) { + return throwIfImpossible ? bucket.removeOne(value) : bucket.removeOneOrNop(value); + } + + @Override + public default Value asSingleton(MarkedMemory.MarkedMultiset bucket) { + if (bucket.size() != 1) return null; + Value candidate = bucket.iterator().next(); + return bucket.getCount(candidate) == 1 ? candidate : null; + } + + @Override + public default MarkedMemory.MarkedMultiset createSingletonBucket(Value value) { + MarkedMemory.MarkedMultiset result = createMarkedMultiset(); + result.addOne(value); + return result; + } + + @Override + public default MarkedMemory.MarkedMultiset createDeltaBucket(Value positive, Value negative) { + throw new IllegalStateException(); + } + } + + + // TODO add ToDeltaBagsAbstract + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMultiset.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMultiset.java new file mode 100644 index 00000000..bdd5d597 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/IMultiset.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util; + +/** + * An {@link IMemory} that always contains values with a nonnegative multiplicity. + * + *

    In case a write operation caused underflow, an {@link IllegalStateException} is thrown. + * + * @author Gabor Bergmann + * @since 1.7 + */ +public interface IMultiset extends IMemory { + + /** + * Adds the given number of occurrences to the memory. The count value must be a positive number. + * + * @param count + * the number of occurrences + * @return true if the tuple was not present before in the memory + */ + boolean addPositive(T value, int count); + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/IProvider.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/IProvider.java new file mode 100644 index 00000000..cd25dc95 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/IProvider.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util; + +import java.util.function.Function; +import java.util.function.Supplier; + +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; + +/** + * A provider interface useful in various registry instances. + * + * @author Zoltan Ujhelyi + * + */ +public interface IProvider extends Supplier{ + + public final class ProvidedValueFunction implements Function, PQuery> { + @Override + public PQuery apply(IProvider input) { + return (input == null) ? null : input.get(); + } + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/ISetMemory.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/ISetMemory.java new file mode 100644 index 00000000..0c03da48 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/ISetMemory.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util; + +import java.util.function.BiConsumer; + +/** + * An {@link IMemory} that always contains values with a 0 or +1 multiplicity. + * + *

    In case a write operation causes underflow or overflow, an {@link IllegalStateException} is thrown. + * + * @author Gabor Bergmann + * @since 2.0 + */ +public interface ISetMemory extends IMemory { + + @Override + default void forEachEntryWithMultiplicities(BiConsumer entryConsumer) { + for (T t : this.distinctValues()) entryConsumer.accept(t, 1); + } + + + @Override + default boolean removeOne(T value) { + if (!removeOneOrNop(value)) + throw new IllegalStateException(); + return true; + } + + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/MapBackedMemoryView.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/MapBackedMemoryView.java new file mode 100644 index 00000000..3be078bd --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/MapBackedMemoryView.java @@ -0,0 +1,102 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util; + +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.function.BiConsumer; + +/** + * Wraps a Map (mapping elements to non-zero multiplicities) into an {@link IMemoryView}. + * + * @author Gabor Bergmann + * @since 2.0 + */ +public class MapBackedMemoryView implements IMemoryView { + + private Map wrapped; + + /** + * @param wrapped an equivalent map from contained objects to multiplicities + */ + protected MapBackedMemoryView(Map wrapped) { + super(); + this.wrapped = wrapped; + } + + @Override + public Iterator iterator() { + return wrapped.keySet().iterator(); + } + + @Override + public int getCount(T value) { + return getCountUnsafe(value); + } + + @Override + public int getCountUnsafe(Object value) { + Integer count = wrapped.get(value); + return count == null ? 0 : count; + } + + @Override + public boolean containsNonZero(T value) { + return wrapped.containsKey(value); + } + + @Override + public boolean containsNonZeroUnsafe(Object value) { + return wrapped.containsKey(value); + } + + @Override + public int size() { + return wrapped.size(); + } + + @Override + public boolean isEmpty() { + return wrapped.isEmpty(); + } + + @Override + public Set distinctValues() { + return wrapped.keySet(); + } + + + @Override + public void forEachEntryWithMultiplicities(BiConsumer entryConsumer) { + for (Entry entry : wrapped.entrySet()) { + entryConsumer.accept(entry.getKey(), entry.getValue()); + } + } + + @Override + public Iterable> entriesWithMultiplicities() { + return wrapped.entrySet(); + } + + @Override + public int hashCode() { + return IMemoryView.hashCode(this); + } + @Override + public boolean equals(Object obj) { + return IMemoryView.equals(this, obj); + } + + @Override + public String toString() { + return wrapped.toString(); + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/MarkedMemory.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/MarkedMemory.java new file mode 100644 index 00000000..d22dcbe7 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/MarkedMemory.java @@ -0,0 +1,21 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util; + +/** + * Internal marker type, must only be instantiated inside implementors of IMultiLookupImpl + * @noimplement This interface is not intended to be implemented by clients. + * @since 2.0 + */ +public interface MarkedMemory extends IMemory { + + static interface MarkedSet extends MarkedMemory {} + static interface MarkedMultiset extends MarkedMemory {} + static interface MarkedDeltaBag extends MarkedMemory {} +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/MemoryViewBackedMapView.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/MemoryViewBackedMapView.java new file mode 100644 index 00000000..49711a89 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/MemoryViewBackedMapView.java @@ -0,0 +1,117 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * A partial and read-only Map implementation, mapping elements to multiplicities backed by an {@link IMemoryView}. + * + *

    Not implemented: write methods. + * + *

    Inefficiently implemented: {@link #containsValue(Object)}, {@link #values()}, {@link #entrySet()}. + * + * @author Gabor Bergmann + * @since 2.0 + */ +public class MemoryViewBackedMapView implements Map { + + private static final String READ_ONLY = "Read only"; + private final IMemoryView wrapped; + + /** + * @param wrapped a memory view whose contents are to be exposed as an element-to-integer map. + */ + protected MemoryViewBackedMapView(IMemoryView wrapped) { + super(); + this.wrapped = wrapped; + } + + @Override + public int size() { + return wrapped.size(); + } + + @Override + public boolean isEmpty() { + return wrapped.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return wrapped.containsNonZeroUnsafe(key); + } + + @Override + public boolean containsValue(Object value) { + if (value instanceof Integer) { + for (Entry entry : wrapped.entriesWithMultiplicities()) { + if (entry.getValue().equals(value)) return true; + } + } + return false; + } + + @Override + public Integer put(T key, Integer value) { + throw new UnsupportedOperationException(READ_ONLY); + } + + @Override + public Integer get(Object key) { + int count = wrapped.getCountUnsafe(key); + if (count == 0) return null; else return count; + } + + @Override + public Integer remove(Object key) { + throw new UnsupportedOperationException(READ_ONLY); + } + + @Override + public void putAll(Map m) { + throw new UnsupportedOperationException(READ_ONLY); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(READ_ONLY); + } + + @Override + public Set keySet() { + return wrapped.distinctValues(); + } + + @Override + public Collection values() { + Collection result = new ArrayList<>(); + wrapped.forEachEntryWithMultiplicities((value, count) -> result.add(count)); + return result; + } + + @Override + public Set> entrySet() { + Set> result = new HashSet<>(); + for (Entry entry : wrapped.entriesWithMultiplicities()) { + result.add(entry); + } + return result; + } + + + @Override + public String toString() { + return wrapped.toString(); + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/Preconditions.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/Preconditions.java new file mode 100644 index 00000000..e9e5e3a0 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/Preconditions.java @@ -0,0 +1,208 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util; + +import java.util.function.Supplier; + +/** + * This class was motivated by the similar Preconditions class from Guava to provide simple precondition checking + * functionality. However, as starting with version 2.0 the runtime of VIATRA Query should not depend on Guava, the + * relevant functionality of the Preconditions checking functionality will be implemented here. + * + * @author Zoltan Ujhelyi + * @since 2.0 + * + */ +public final class Preconditions { + + private Preconditions() { + /* Utility class constructor */ } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + * @param expression + * a boolean expression + * @throws IllegalArgumentException + * if {@code expression} is false + */ + public static void checkArgument(boolean expression) { + if (!expression) { + throw new IllegalArgumentException(); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + * @param expression + * a boolean expression + * @param errorMessage + * the exception message to use if the check fails + * @throws IllegalArgumentException + * if {@code expression} is false + */ + public static void checkArgument(boolean expression, String errorMessage) { + if (!expression) { + throw new IllegalArgumentException(errorMessage); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + * @param expression + * a boolean expression + * @param errorMessageTemplate + * a template for the exception message should the check fail using the Java Formatter syntax; the same + * as used by {@link String#format(String, Object...)}. + * @param errorMessageArgs + * the arguments to be substituted into the message template. + * @throws IllegalArgumentException + * if {@code expression} is false + * @throws NullPointerException + * if the check fails and either {@code errorMessageTemplate} or {@code errorMessageArgs} is null (don't + * let this happen) + */ + public static void checkArgument(boolean expression, String errorMessageTemplate, Object... errorMessageArgs) { + if (!expression) { + throw new IllegalArgumentException(String.format(errorMessageTemplate, errorMessageArgs)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + * @param expression + * a boolean expression + * @param messageSupplier a supplier that is called to calculate the error message if necessary + * @throws IllegalArgumentException + * if {@code expression} is false + */ + public static void checkArgument(boolean expression, Supplier messageSupplier) { + if (!expression) { + throw new IllegalArgumentException(messageSupplier.get()); + } + } + + /** + * Ensures the truth of an expression involving one or more fields of a class. + * + * @param expression + * a boolean expression + * @throws IllegalStateException + * if {@code expression} is false + */ + public static void checkState(boolean expression) { + if (!expression) { + throw new IllegalStateException(); + } + } + + /** + * Ensures the truth of an expression involving one or more fields of a class. + * + * @param expression + * a boolean expression + * @param errorMessage + * the exception message to use if the check fails + * @throws IllegalStateException + * if {@code expression} is false + */ + public static void checkState(boolean expression, String errorMessage) { + if (!expression) { + throw new IllegalStateException(errorMessage); + } + } + + /** + * Ensures the truth of an expression involving one or more fields of a class. + * + * @param expression + * a boolean expression + * @param errorMessageTemplate + * a template for the exception message should the check fail using the Java Formatter syntax; the same + * as used by {@link String#format(String, Object...)}. + * @param errorMessageArgs + * the arguments to be substituted into the message template. + * @throws IllegalStateException + * if {@code expression} is false + * @throws NullPointerException + * if the check fails and either {@code errorMessageTemplate} or {@code errorMessageArgs} is null (don't + * let this happen) + */ + public static void checkState(boolean expression, String errorMessageTemplate, Object... errorMessageArgs) { + if (!expression) { + throw new IllegalStateException(String.format(errorMessageTemplate, errorMessageArgs)); + } + } + + /** + * Ensures the truth of an expression involving one or more fields of a class. + * + * @param expression + * a boolean expression + * @param messageSupplier a supplier that is called to calculate the error message if necessary + * @throws IllegalStateException + * if {@code expression} is false + */ + public static void checkState(boolean expression, Supplier messageSupplier) { + if (!expression) { + throw new IllegalStateException(messageSupplier.get()); + } + } + + /** + * Ensures that an index is appropriate for a list or array of given size. + * + * @param index + * @param size + * @throws IndexOutOfBoundsException + * if index is negative or is greater or equal to size + */ + public static void checkElementIndex(int index, int size) { + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException(); + } + } + + /** + * Ensures that an index is appropriate for a list or array of given size. + * + * @param index + * @param size + * @param errorMessageTemplate + * a template for the exception message should the check fail using the Java Formatter syntax; the same + * as used by {@link String#format(String, Object...)}. + * @param errorMessageArgs + * the arguments to be substituted into the message template. + * @throws IndexOutOfBoundsException + * if index is negative or is greater or equal to size + */ + public static void checkElementIndex(int index, int size, String errorMessageTemplate, Object... errorMessageArgs) { + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException(String.format(errorMessageTemplate, errorMessageArgs)); + } + } + + /** + * Ensures that an index is appropriate for a list or array of given size. + * + * @param index + * @param size + * @param messageSupplier a supplier that is called to calculate the error message if necessary + * @throws IndexOutOfBoundsException + * if index is negative or is greater or equal to size + */ + public static void checkElementIndex(int index, int size, Supplier messageSupplier) { + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException(messageSupplier.get()); + } + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/PurgableCache.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/PurgableCache.java new file mode 100644 index 00000000..c4e6b5af --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/PurgableCache.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; + +/** + * @author Zoltan Ujhelyi + * @since 1.7 + * @noreference This class is not intended to be referenced by clients. + */ +public class PurgableCache implements ICache { + + Map storage = new HashMap<>(); + + @Override + @SuppressWarnings("unchecked") + public T getValue(Object key, Class clazz, Supplier valueProvider) { + if (storage.containsKey(key)) { + Object value = storage.get(key); + Preconditions.checkState(clazz.isInstance(value), "Cache stores for key %s a value of %s that is incompatible with the requested type %s", key, value, clazz); + return (T) value; + } else { + T value = valueProvider.get(); + storage.put(key, value); + return value; + } + } + + /** + * Removes all values stored in the cache + */ + public void purge() { + storage.clear(); + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/Sets.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/Sets.java new file mode 100644 index 00000000..3749fe06 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/Sets.java @@ -0,0 +1,90 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Gabor Bergmann - initial API and implementation + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * This class was motivated by the similar Sets class from Guava to provide simple set manipulation + * functionality. However, as starting with version 2.3 the runtime of VIATRA Query should not depend on Guava, + * not even internally, the relevant subset of Sets methods will be reimplemented here. + * + *

    The current approach is to delegate to Eclipse Collections wherever possible. + * Such glue methods are useful so that downstream clients can avoid directly depending on Eclipse Collections. + * + *

    Without an equivalent from Eclipse Collections, {@link #cartesianProduct(List)} is implemented here from scratch. + * + * @author Gabor Bergmann + * @since 2.3 + */ +public final class Sets { + + /** + * @since 2.4 + */ + public static Set newSet(Iterable elements) { + return org.eclipse.collections.impl.factory.Sets.mutable.ofAll(elements); + } + + public static Set intersection(Set left, Set right) { + return org.eclipse.collections.impl.factory.Sets.intersect(left, right); + } + + public static Set difference(Set left, Set right) { + return org.eclipse.collections.impl.factory.Sets.difference(left, right); + } + + public static Set union(Set left, Set right) { + return org.eclipse.collections.impl.factory.Sets.union(left, right); + } + + public static Set> powerSet(Set set) { + return org.eclipse.collections.impl.factory.Sets.powerSet(set); + } + + public static Set> cartesianProduct(List> setsList) { + + class Suffix { // simple immutable linked list + private A head; + private Suffix next; + + public Suffix(A head, Suffix next) { + super(); + this.head = head; + this.next = next; + } + + public List toList() { + ArrayList result = new ArrayList<>(); + for (Suffix cursor = this; cursor!=null; cursor = cursor.next) + result.add(cursor.head); + return result; + } + } + + // build result lists from end to start, in the form of suffixes + Stream suffixes = Stream.of((Suffix) null /* empty suffix*/); + for (int i = setsList.size()-1; i>=0; --i) { // iterate sets in reverse order + Set set = setsList.get(i); + suffixes = suffixes.flatMap(suffix -> set.stream().map(newElement -> new Suffix(newElement, suffix))); + } + + + return suffixes.map(Suffix::toList).collect(Collectors.toSet()); + } + + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/Signed.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/Signed.java new file mode 100644 index 00000000..8f8bc228 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/Signed.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util; + +import java.util.Objects; + +/** + * A piece of data associated with a direction. + * + * @author Tamas Szabo + * @since 2.4 + */ +public class Signed> { + + private final Payload payload; + private final Direction direction; + + public Signed(final Direction direction, final Payload payload) { + this.payload = payload; + this.direction = direction; + } + + public Payload getPayload() { + return payload; + } + + public Direction getDirection() { + return direction; + } + + @Override + public int hashCode() { + return Objects.hash(direction, payload); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } else if (obj == null || this.getClass() != obj.getClass()) { + return false; + } else { + @SuppressWarnings("rawtypes") + final Signed other = (Signed) obj; + return direction == other.direction && Objects.equals(payload, other.payload); + } + } + + @Override + public String toString() { + return this.direction.asSign() + this.payload.toString(); + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/SingletonInstanceProvider.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/SingletonInstanceProvider.java new file mode 100644 index 00000000..cc5963f7 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/SingletonInstanceProvider.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util; + +/** + * A provider implementation that always returns the same object instance. + * @author Zoltan Ujhelyi + */ +public class SingletonInstanceProvider implements IProvider{ + + private T instance; + + public SingletonInstanceProvider(T instance) { + Preconditions.checkArgument(instance != null, "Instance parameter must not be null."); + this.instance = instance; + } + + @Override + public T get() { + return instance; + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/SingletonMemoryView.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/SingletonMemoryView.java new file mode 100644 index 00000000..b303f9ad --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/SingletonMemoryView.java @@ -0,0 +1,105 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util; + +import java.util.Collections; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Set; + +/** + * An immutable memory view that consists of a single non-null element with multiplicity 1. + * @author Gabor Bergmann + * @since 2.0 + */ +public final class SingletonMemoryView implements IMemoryView { + + private Value wrapped; + private static final int ONE_HASH = Integer.valueOf(1).hashCode(); + + public SingletonMemoryView(Value value) { + this.wrapped = value; + } + + @Override + public Iterator iterator() { + return new Iterator() { + boolean hasNext = true; + + @Override + public boolean hasNext() { + return hasNext; + } + + @Override + public Value next() { + if (hasNext) { + hasNext = false; + return wrapped; + } else throw new NoSuchElementException(); + } + }; + } + + @Override + public int getCount(Value value) { + return wrapped.equals(value) ? 1 : 0; + } + + @Override + public int getCountUnsafe(Object value) { + return wrapped.equals(value) ? 1 : 0; + } + + @Override + public boolean containsNonZero(Value value) { + return wrapped.equals(value); + } + + @Override + public boolean containsNonZeroUnsafe(Object value) { + return wrapped.equals(value); + } + + @Override + public int size() { + return 1; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public Set distinctValues() { + return Collections.singleton(wrapped); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof IMemoryView) { + IMemoryView other = (IMemoryView) obj; + if (1 != other.size()) return false; + if (1 != other.getCountUnsafe(wrapped)) return false; + return true; + } + return false; + } + + @Override + public int hashCode() { + return wrapped.hashCode() ^ ONE_HASH; + } + + @Override + public String toString() { + return "{" + wrapped + "}"; + } +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/TimelyMemory.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/TimelyMemory.java new file mode 100644 index 00000000..90fcad4d --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/TimelyMemory.java @@ -0,0 +1,517 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util; + +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NavigableMap; +import java.util.Set; +import java.util.TreeMap; + +import tools.refinery.viatra.runtime.matchers.tuple.ITuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.resumable.Resumable; +import tools.refinery.viatra.runtime.matchers.util.resumable.UnmaskedResumable; +import tools.refinery.viatra.runtime.matchers.util.timeline.Diff; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timelines; + +/** + * A timely memory implementation that incrementally maintains the {@link Timeline}s of tuples. The memory is capable of + * lazy folding (see {@link Resumable}). + * + * @author Tamas Szabo + * @since 2.3 + */ +public class TimelyMemory> implements Clearable, UnmaskedResumable { + + protected final Map> counters; + protected final Map> timelines; + public final TreeMap> foldingState; + protected final Set presentAtInfinity; + protected final boolean isLazy; + protected final Diff EMPTY_DIFF = new Diff(); + + public TimelyMemory() { + this(false); + } + + public TimelyMemory(final boolean isLazy) { + this.counters = CollectionsFactory.createMap(); + this.timelines = CollectionsFactory.createMap(); + this.presentAtInfinity = CollectionsFactory.createSet(); + this.isLazy = isLazy; + if (isLazy) { + this.foldingState = CollectionsFactory.createTreeMap(); + } else { + this.foldingState = null; + } + } + + @Override + public Set getResumableTuples() { + if (this.foldingState == null || this.foldingState.isEmpty()) { + return Collections.emptySet(); + } else { + return this.foldingState.firstEntry().getValue().keySet(); + } + } + + @Override + public Timestamp getResumableTimestamp() { + if (this.foldingState == null || this.foldingState.isEmpty()) { + return null; + } else { + return this.foldingState.firstKey(); + } + } + + /** + * Registers the given folding state for the specified timestamp and tuple. If there is already a state stored, the + * two states will be merged together. + */ + protected void addFoldingState(final Tuple tuple, final FoldingState state, final Timestamp timestamp) { + assert state.diff != 0; + final Map tupleMap = this.foldingState.computeIfAbsent(timestamp, + k -> CollectionsFactory.createMap()); + tupleMap.compute(tuple, (k, v) -> { + return v == null ? state : v.merge(state); + }); + } + + @Override + public Map> resumeAt(final Timestamp timestamp) { + Timestamp current = this.getResumableTimestamp(); + if (current == null) { + throw new IllegalStateException("There is othing to fold!"); + } else if (current.compareTo(timestamp) != 0) { + // It can happen that already registered folding states end up having zero diffs, + // and we are instructed to continue folding at a timestamp that is higher + // than the lowest timestamp with a folding state. + // However, we only do garbage collection in doFoldingState, so now it is time to + // first clean up those states with zero diffs. + while (current != null && current.compareTo(timestamp) < 0) { + final Map tupleMap = this.foldingState.remove(current); + for (final Entry entry : tupleMap.entrySet()) { + final Tuple key = entry.getKey(); + final FoldingState value = entry.getValue(); + if (value.diff != 0) { + throw new IllegalStateException("Expected zero diff during garbage collection at " + current + + ", but the diff was " + value.diff + "!"); + } + doFoldingStep(key, value, current); + } + current = this.getResumableTimestamp(); + } + if (current == null || current.compareTo(timestamp) != 0) { + throw new IllegalStateException("Expected to continue folding at " + timestamp + "!"); + } + } + + final Map> diffMap = CollectionsFactory.createMap(); + final Map tupleMap = this.foldingState.remove(timestamp); + for (final Entry entry : tupleMap.entrySet()) { + final Tuple key = entry.getKey(); + final FoldingState value = entry.getValue(); + diffMap.put(key, doFoldingStep(key, value, timestamp)); + } + + if (this.foldingState.get(timestamp) != null) { + throw new IllegalStateException( + "Folding at " + timestamp + " produced more folding work at the same timestamp!"); + } + + return diffMap; + } + + protected Diff doFoldingStep(final Tuple tuple, final FoldingState state, final Timestamp timestamp) { + final CumulativeCounter counter = getCounter(tuple, timestamp); + if (state.diff == 0) { + gcCounters(counter, tuple, timestamp); + return EMPTY_DIFF; + } else { + final Diff resultDiff = new Diff<>(); + final Timestamp nextTimestamp = this.counters.get(tuple).higherKey(timestamp); + + final int oldCumulative = counter.cumulative; + + counter.cumulative += state.diff; + + computeDiffsLazy(state.diff < 0 ? Direction.DELETE : Direction.INSERT, oldCumulative, counter.cumulative, + timestamp, nextTimestamp, resultDiff); + + gcCounters(counter, tuple, timestamp); + updateTimeline(tuple, resultDiff); + + // prepare folding state for next timestamp + if (nextTimestamp != null) { + // propagate the incoming diff, not the diff stored in counter + addFoldingState(tuple, new FoldingState(state.diff), nextTimestamp); + } + + return resultDiff; + } + } + + /** + * On-demand initializes and returns the counter for the given tuple and timestamp. + */ + protected CumulativeCounter getCounter(final Tuple tuple, final Timestamp timestamp) { + final TreeMap counterTimeline = this.counters.computeIfAbsent(tuple, + k -> CollectionsFactory.createTreeMap()); + + final CumulativeCounter counter = counterTimeline.computeIfAbsent(timestamp, k -> { + final Entry previousCounter = counterTimeline.lowerEntry(k); + final int previousCumulative = previousCounter == null ? 0 : previousCounter.getValue().cumulative; + return new CumulativeCounter(0, previousCumulative); + }); + + return counter; + } + + /** + * Garbage collects the counter of the given tuple and timestamp if the new diff is zero. + */ + protected void gcCounters(final CumulativeCounter counter, final Tuple tuple, final Timestamp timestamp) { + if (counter.diff == 0) { + final TreeMap counterMap = this.counters.get(tuple); + counterMap.remove(timestamp); + if (counterMap.isEmpty()) { + this.counters.remove(tuple); + } + } + } + + /** + * Utility method that computes the timeline diffs in case of lazy memories. The diffs will be inserted into the + * input parameter. This method computes diffs for entire plateaus that spans from timestamp to nextTimestamp. + * + * Compared to the eager version of this method, the lazy version makes use of both the old and the new cumulative + * values because it can happen that the cumulative is incremented by a value that is larger than 1 (as folding + * states are merged together). This means that we cant decide whether the cumulative became positive by comparing + * the new value to 1. + */ + protected void computeDiffsLazy(final Direction direction, final int oldCumulative, final int newCumulative, + final Timestamp timestamp, final Timestamp nextTimestamp, final Diff diffs) { + if (direction == Direction.INSERT) { + if (newCumulative == 0) { + throw new IllegalStateException("Cumulative count can never be negative!"); + } else { + if (oldCumulative == 0 /* current became positive */) { + // (1) either we sent out a DELETE before and now we need to cancel it, + // (2) or we just INSERT this for the first time + diffs.add(new Signed<>(Direction.INSERT, timestamp)); + if (nextTimestamp != null) { + diffs.add(new Signed<>(Direction.DELETE, nextTimestamp)); + } + } else /* current stays positive */ { + // nothing to do + } + } + } else { + if (newCumulative < 0) { + throw new IllegalStateException("Cumulative count can never be negative!"); + } else { + if (newCumulative == 0 /* current became zero */) { + diffs.add(new Signed<>(Direction.DELETE, timestamp)); + if (nextTimestamp != null) { + diffs.add(new Signed<>(Direction.INSERT, nextTimestamp)); + } + } else /* current stays positive */ { + // nothing to do + } + } + } + } + + /** + * Utility method that computes the timeline diffs in case of eager memories. The diffs will be inserted into the + * input parameter. This method computes diffs that describe momentary changes instead of plateaus. Returns a + * {@link SignChange} that describes how the sign has changed at the given timestamp. + */ + protected SignChange computeDiffsEager(final Direction direction, final CumulativeCounter counter, + final SignChange signChangeAtPrevious, final Timestamp timestamp, final Diff diffs) { + if (direction == Direction.INSERT) { + if (counter.cumulative == 0) { + throw new IllegalStateException("Cumulative count can never be negative!"); + } else { + if (counter.cumulative == 1 /* current became positive */) { + if (signChangeAtPrevious != SignChange.BECAME_POSITIVE) { + // (1) either we sent out a DELETE before and now we need to cancel it, + // (2) or we just INSERT this for the first time + diffs.add(new Signed<>(Direction.INSERT, timestamp)); + } else { + // we have already emitted this at the previous timestamp + // both previous and current became positive + throw new IllegalStateException( + "This would mean that the diff at current is 0 " + counter.diff); + } + + // remember for next timestamp + return SignChange.BECAME_POSITIVE; + } else /* current stays positive */ { + if (signChangeAtPrevious == SignChange.BECAME_POSITIVE) { + // we sent out an INSERT before and now the timeline is positive already starting at previous + // we need to cancel the effect of this with a DELETE + diffs.add(new Signed<>(Direction.DELETE, timestamp)); + } else { + // this is normal, both previous and current was positive and stays positive + } + + // remember for next timestamp + return SignChange.IRRELEVANT; + } + } + } else { + if (counter.cumulative < 0) { + throw new IllegalStateException("Cumulative count can never be negative!"); + } else { + if (counter.cumulative == 0 /* current became zero */) { + if (signChangeAtPrevious != SignChange.BECAME_ZERO) { + // (1) either we sent out a INSERT before and now we need to cancel it, + // (2) or we just DELETE this for the first time + diffs.add(new Signed<>(Direction.DELETE, timestamp)); + } else { + // we have already emitted this at the previous timestamp + // both previous and current became zero + throw new IllegalStateException( + "This would mean that the diff at current is 0 " + counter.diff); + } + + // remember for next timestamp + return SignChange.BECAME_ZERO; + } else /* current stays positive */ { + if (signChangeAtPrevious == SignChange.BECAME_ZERO) { + // we sent out a DELETE before and now the timeline is zero already starting at previous + // we need to cancel the effect of this with a INSERT + diffs.add(new Signed<>(Direction.INSERT, timestamp)); + } else { + // this is normal, both previous and current was positive and stays positive + } + + // remember for next timestamp + return SignChange.IRRELEVANT; + } + } + } + } + + public Diff put(final Tuple tuple, final Timestamp timestamp) { + if (this.isLazy) { + return putLazy(tuple, timestamp); + } else { + return putEager(tuple, timestamp); + } + } + + public Diff remove(final Tuple tuple, final Timestamp timestamp) { + if (this.isLazy) { + return removeLazy(tuple, timestamp); + } else { + return removeEager(tuple, timestamp); + } + } + + protected Diff putEager(final Tuple tuple, final Timestamp timestamp) { + final Diff resultDiff = new Diff<>(); + final CumulativeCounter counter = getCounter(tuple, timestamp); + ++counter.diff; + + // before the INSERT timestamp, no change at all + // it cannot happen that those became positive in this round + SignChange signChangeAtPrevious = SignChange.IRRELEVANT; + + final NavigableMap nextCounters = this.counters.get(tuple).tailMap(timestamp, + true); + for (final Entry currentEntry : nextCounters.entrySet()) { + final Timestamp currentTimestamp = currentEntry.getKey(); + final CumulativeCounter currentCounter = currentEntry.getValue(); + ++currentCounter.cumulative; + signChangeAtPrevious = computeDiffsEager(Direction.INSERT, currentCounter, signChangeAtPrevious, + currentTimestamp, resultDiff); + } + + gcCounters(counter, tuple, timestamp); + updateTimeline(tuple, resultDiff); + + return resultDiff; + } + + protected Diff putLazy(final Tuple tuple, final Timestamp timestamp) { + final CumulativeCounter counter = getCounter(tuple, timestamp); + counter.diff += 1; + // before the INSERT timestamp, no change at all + // it cannot happen that those became positive in this round + addFoldingState(tuple, new FoldingState(+1), timestamp); + return EMPTY_DIFF; + } + + protected Diff removeEager(final Tuple tuple, final Timestamp timestamp) { + final Diff resultDiff = new Diff<>(); + final CumulativeCounter counter = getCounter(tuple, timestamp); + --counter.diff; + + // before the DELETE timestamp, no change at all + // it cannot happen that those became zero in this round + SignChange signChangeAtPrevious = SignChange.IRRELEVANT; + + final NavigableMap nextCounters = this.counters.get(tuple).tailMap(timestamp, + true); + for (final Entry currentEntry : nextCounters.entrySet()) { + final Timestamp currentTimestamp = currentEntry.getKey(); + final CumulativeCounter currentCounter = currentEntry.getValue(); + --currentCounter.cumulative; + signChangeAtPrevious = computeDiffsEager(Direction.DELETE, currentCounter, signChangeAtPrevious, + currentTimestamp, resultDiff); + } + + gcCounters(counter, tuple, timestamp); + updateTimeline(tuple, resultDiff); + + return resultDiff; + } + + protected Diff removeLazy(final Tuple tuple, final Timestamp timestamp) { + final CumulativeCounter counter = getCounter(tuple, timestamp); + counter.diff -= 1; + // before the DELETE timestamp, no change at all + // it cannot happen that those became zero in this round + addFoldingState(tuple, new FoldingState(-1), timestamp); + return EMPTY_DIFF; + } + + /** + * Updates and garbage collects the timeline of the given tuple based on the given timeline diff. + */ + protected void updateTimeline(final Tuple tuple, final Diff diff) { + if (!diff.isEmpty()) { + this.timelines.compute(tuple, (k, oldTimeline) -> { + this.presentAtInfinity.remove(tuple); + final Timeline timeline = oldTimeline == null ? Timelines.createFrom(diff) + : oldTimeline.mergeAdditive(diff); + if (timeline.isPresentAtInfinity()) { + this.presentAtInfinity.add(tuple); + } + if (timeline.isEmpty()) { + return null; + } else { + return timeline; + } + }); + } + } + + /** + * @since 2.8 + */ + public Set getTuplesAtInfinity() { + return this.presentAtInfinity; + } + + /** + * Returns the number of tuples that are present at the moment 'infinity'. + */ + public int getCountAtInfinity() { + return this.presentAtInfinity.size(); + } + + /** + * Returns true if the given tuple is present at the moment 'infinity'. + */ + public boolean isPresentAtInfinity(final Tuple tuple) { + final Timeline timeline = this.timelines.get(tuple); + if (timeline == null) { + return false; + } else { + return timeline.isPresentAtInfinity(); + } + } + + public boolean isEmpty() { + return this.counters.isEmpty(); + } + + public int size() { + return this.counters.size(); + } + + public Set keySet() { + return this.counters.keySet(); + } + + public Map> asMap() { + return this.timelines; + } + + public Timeline get(final ITuple tuple) { + return this.timelines.get(tuple); + } + + @Override + public void clear() { + this.counters.clear(); + this.timelines.clear(); + if (this.foldingState != null) { + this.foldingState.clear(); + } + } + + public boolean containsKey(final ITuple tuple) { + return this.counters.containsKey(tuple); + } + + @Override + public String toString() { + return this.counters + "\n" + this.timelines + "\n" + this.foldingState + "\n"; + } + + protected static final class CumulativeCounter { + protected int diff; + protected int cumulative; + + protected CumulativeCounter(final int diff, final int cumulative) { + this.diff = diff; + this.cumulative = cumulative; + } + + @Override + public String toString() { + return "{diff=" + this.diff + ", cumulative=" + this.cumulative + "}"; + } + + } + + protected static final class FoldingState { + protected final int diff; + + protected FoldingState(final int diff) { + this.diff = diff; + } + + @Override + public String toString() { + return "{diff=" + this.diff + "}"; + } + + /** + * The returned result will never be null, even if the resulting diff is zero. + */ + public FoldingState merge(final FoldingState that) { + Preconditions.checkArgument(that != null); + return new FoldingState(this.diff + that.diff); + } + + } + + protected enum SignChange { + BECAME_POSITIVE, BECAME_ZERO, IRRELEVANT; + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/resumable/MaskedResumable.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/resumable/MaskedResumable.java new file mode 100644 index 00000000..ea70e61d --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/resumable/MaskedResumable.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util.resumable; + +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.timeline.Diff; + +/** + * A masked {@link Resumable} implementation, which maintains lazy folding per tuple signature. + * + * @author Tamas Szabo + * @since 2.4 + */ +public interface MaskedResumable> extends Resumable { + + /** + * When called, the folding of the state shall be resumed at the given timestamp. The resumable is expected to + * do a folding step at the given timestamp only. Afterwards, folding shall be interrupted, even if there is more + * folding to do towards higher timestamps. + */ + public Map>> resumeAt(final Timestamp timestamp); + + /** + * Returns the set of signatures for which lazy folding shall be resumed at the next timestamp. + */ + public Iterable getResumableSignatures(); + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/resumable/Resumable.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/resumable/Resumable.java new file mode 100644 index 00000000..2861df20 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/resumable/Resumable.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util.resumable; + +/** + * A resumable lazily folds its state towards higher timestamps. Folding shall be done in the increasing order of + * timestamps, and it shall be interrupted after each step. The resumable can then be instructed to resume the folding, + * one step at a time. + * + * @author Tamas Szabo + * @since 2.4 + */ +public interface Resumable> { + + /** + * Returns the smallest timestamp where lazy folding shall be resumed, or null if there is no more folding to do in this + * resumable. + */ + public Timestamp getResumableTimestamp(); + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/resumable/UnmaskedResumable.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/resumable/UnmaskedResumable.java new file mode 100644 index 00000000..1671940b --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/resumable/UnmaskedResumable.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util.resumable; + +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.timeline.Diff; + +/** + * A unmasked {@link Resumable} implementation, which maintains lazy folding without caring about tuple signatures. + * + * @author Tamas Szabo + * @since 2.4 + */ +public interface UnmaskedResumable> extends Resumable { + + /** + * When called, the folding of the state shall be resumed at the given timestamp. The resumable is expected to + * do a folding step at the given timestamp only. Afterwards, folding shall be interrupted, even if there is more + * folding to do towards higher timestamps. + */ + public Map> resumeAt(final Timestamp timestamp); + + /** + * Returns the set of tuples for which lazy folding shall be resumed at the next timestamp. + */ + public Iterable getResumableTuples(); + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/CompactTimeline.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/CompactTimeline.java new file mode 100644 index 00000000..0532d094 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/CompactTimeline.java @@ -0,0 +1,111 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util.timeline; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.Signed; + +/** + * A compact timeline may cosist of an arbitrary amount of moments. + * It is backed by an {@link ArrayList}. + * + * @author Tamas Szabo + * @since 2.4 + */ +public class CompactTimeline> extends Timeline { + + protected final List elements; + + CompactTimeline() { + this.elements = new ArrayList<>(); + } + + CompactTimeline(final Timestamp timestamp) { + this(); + this.elements.add(timestamp); + } + + CompactTimeline(final List timestamps) { + this.elements = new ArrayList<>(timestamps.size()); + this.elements.addAll(timestamps); + } + + CompactTimeline(final Diff diff) { + this.elements = new ArrayList<>(diff.size()); + Direction expected = Direction.INSERT; + for (Signed signed : diff) { + if (!expected.equals(signed.getDirection())) { + throw new IllegalStateException(String.format("Expected direction (%s) constraint violated! %s @%s", + expected, diff, signed.getPayload())); + } + this.elements.add(signed.getPayload()); + expected = expected.opposite(); + } + } + + @Override + public Signed getSigned(final int index) { + final Direction direction = index % 2 == 0 ? Direction.INSERT : Direction.DELETE; + return new Signed<>(direction, this.getUnsigned(index)); + } + + @Override + public Timestamp getUnsigned(final int index) { + if (this.elements.size() <= index) { + throw new IllegalArgumentException( + "Timeline size (" + this.size() + ") is smaller than requested index " + index + "!"); + } else { + return this.elements.get(index); + } + } + + @Override + public int size() { + return this.elements.size(); + } + + @Override + public boolean isPresentAtInfinity() { + // if it has an odd length, then it ends with "INSERT" + return this.size() % 2 == 1; + } + + @Override + public Iterable> asChangeSequence() { + Iterable outer = this.elements; + return () -> { + final Iterator itr = outer.iterator(); + return new Iterator>() { + Direction direction = Direction.INSERT; + + @Override + public boolean hasNext() { + return itr.hasNext(); + } + + @Override + public Signed next() { + final Signed result = new Signed(direction, itr.next()); + direction = direction.opposite(); + return result; + } + }; + }; + } + + @Override + public boolean isEmpty() { + return this.elements.isEmpty(); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/Diff.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/Diff.java new file mode 100644 index 00000000..cec6049e --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/Diff.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util.timeline; + +import java.util.ArrayList; + +import tools.refinery.viatra.runtime.matchers.util.Signed; + +/** + * The description of a delta that specifies how a {@link Timeline} changes. It consists of {@link Signed} timestamps that + * depict the moments of insertions and deletions on the timeline. + * + * @author Tamas Szabo + * @since 2.4 + * @param + * the type representing the timestamps + */ +public class Diff> extends ArrayList> { + + private static final long serialVersionUID = 3853460426655994160L; + + public Diff() { + + } + + public void appendWithCancellation(Signed item) { + if (this.isEmpty()) { + this.add(item); + } else { + final Signed last = this.get(this.size() - 1); + final int lastMinusItem = last.getPayload().compareTo(item.getPayload()); + if (lastMinusItem == 0) { + if (last.getDirection() != item.getDirection()) { + // cancellation + this.remove(this.size() - 1); + } else { + throw new IllegalStateException( + "Trying to insert or delete for the second time at the same timestamp! " + item); + } + } else if (lastMinusItem > 0) { + throw new IllegalStateException( + "Trying to append a timestamp that is smaller than the last one! " + last + " " + item); + } else { + this.add(item); + } + } + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/SingletonTimeline.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/SingletonTimeline.java new file mode 100644 index 00000000..526a95f5 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/SingletonTimeline.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util.timeline; + +import java.util.Collections; + +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.Signed; + +/** + * A timeline which solely consists of one timestamp value, representing a single insertion. Intuitively, a singleton + * timeline always represents a bump which starts at the given timestamp and lasts till plus infinity. + * + * @author Tamas Szabo + * @since 2.4 + */ +public class SingletonTimeline> extends Timeline { + + protected final Timestamp start; + + SingletonTimeline(final Timestamp timestamp) { + this.start = timestamp; + } + + SingletonTimeline(final Diff diff) { + if (diff.size() != 1 || diff.get(0).getDirection() == Direction.DELETE) { + throw new IllegalArgumentException("There is only a single (insert) timestamp in the singleton timestamp!"); + } else { + this.start = diff.get(0).getPayload(); + } + } + + @Override + public Signed getSigned(final int index) { + return new Signed<>(Direction.INSERT, this.getUnsigned(index)); + } + + @Override + public Timestamp getUnsigned(final int index) { + if (index != 0) { + throw new IllegalArgumentException("There is only a single (insert) timestamp in the singleton timestamp!"); + } else { + return this.start; + } + } + + @Override + public int size() { + return 1; + } + + @Override + public boolean isPresentAtInfinity() { + return true; + } + + @Override + public Iterable> asChangeSequence() { + return Collections.singletonList(this.getSigned(0)); + } + + @Override + public boolean isEmpty() { + return false; + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/Timeline.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/Timeline.java new file mode 100644 index 00000000..9214536c --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/Timeline.java @@ -0,0 +1,146 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util.timeline; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.Signed; + +/** + * A timeline describes the life cycle of a piece of data (typically a tuple in a relation) as a sequence of moments. + * Even moments represent appearances, odd moments represent disappearances. A timeline is immutable, once created, it + * is not possible to extend it with further moments. + * + * @author Tamas Szabo + * @since 2.4 + */ +public abstract class Timeline> { + + public abstract Iterable> asChangeSequence(); + + public abstract boolean isPresentAtInfinity(); + + public abstract boolean isEmpty(); + + public abstract int size(); + + public abstract Signed getSigned(final int index); + + public abstract Timestamp getUnsigned(final int index); + + public Timeline mergeMultiplicative(final Timeline that) { + final List result = new ArrayList<>(); + int thisIdx = 0, thatIdx = 0; + Timestamp thisNext = thisIdx < this.size() ? this.getUnsigned(thisIdx) : null; + Timestamp thatNext = thatIdx < that.size() ? that.getUnsigned(thatIdx) : null; + + while (thisNext != null || thatNext != null) { + int thisMinusThat = 0; + if (thisNext != null && thatNext != null) { + thisMinusThat = thisNext.compareTo(thatNext); + } + if (thisNext == null || thisMinusThat > 0) { + if (thisIdx % 2 == 1) { + result.add(thatNext); + } + thatIdx++; + thatNext = thatIdx < that.size() ? that.getUnsigned(thatIdx) : null; + } else if (thatNext == null || thisMinusThat < 0) { + if (thatIdx % 2 == 1) { + result.add(thisNext); + } + thisIdx++; + thisNext = thisIdx < this.size() ? this.getUnsigned(thisIdx) : null; + } else { + if (thisIdx % 2 == thatIdx % 2) { + result.add(thisNext); + } + thisIdx++; + thatIdx++; + thatNext = thatIdx < that.size() ? that.getUnsigned(thatIdx) : null; + thisNext = thisIdx < this.size() ? this.getUnsigned(thisIdx) : null; + } + } + + return Timelines.createFrom(result); + } + + /** + * Merges this timeline with the given timestamp diff. The expectation is that the resulting timeline starts with an + * insertion. The logic is similar to a merge sort; we iterate side-by-side over the timeline and the diff. During + * the merge, cancellation can happen if at the same timestamp we observe different signs at the corresponding + * timeline and diff elements. + */ + public Timeline mergeAdditive(final Diff diff) { + final Iterator> thisItr = this.asChangeSequence().iterator(); + final Iterator> diffItr = diff.iterator(); + final List result = new ArrayList<>(); + Direction expected = Direction.INSERT; + Signed thisNext = thisItr.hasNext() ? thisItr.next() : null; + Signed diffNext = diffItr.hasNext() ? diffItr.next() : null; + + while (thisNext != null || diffNext != null) { + int thisMinusDiff = 0; + if (thisNext != null && diffNext != null) { + thisMinusDiff = thisNext.getPayload().compareTo(diffNext.getPayload()); + } + + if (thisNext == null || thisMinusDiff > 0) { + if (!expected.equals(diffNext.getDirection())) { + throw new IllegalStateException( + String.format("Expected direction (%s) constraint violated! %s %s @%s", expected, this, + diff, diffNext.getPayload())); + } + result.add(diffNext.getPayload()); + diffNext = diffItr.hasNext() ? diffItr.next() : null; + expected = expected.opposite(); + } else if (diffNext == null || thisMinusDiff < 0) { + if (!expected.equals(thisNext.getDirection())) { + throw new IllegalStateException( + String.format("Expected direction (%s) constraint violated! %s %s @%s", expected, this, + diff, thisNext.getPayload())); + } + result.add(thisNext.getPayload()); + thisNext = thisItr.hasNext() ? thisItr.next() : null; + expected = expected.opposite(); + } else { + // they cancel out each other + if (diffNext.getDirection().equals(thisNext.getDirection())) { + throw new IllegalStateException(String.format("Changes do not cancel out each other! %s %s @%s", + this, diff, thisNext.getPayload())); + } + diffNext = diffItr.hasNext() ? diffItr.next() : null; + thisNext = thisItr.hasNext() ? thisItr.next() : null; + } + } + + return Timelines.createFrom(result); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("["); + boolean first = true; + for (final Signed element : this.asChangeSequence()) { + if (first) { + first = false; + } else { + builder.append(", "); + } + builder.append(element.toString()); + } + builder.append("]"); + return builder.toString(); + } + +} diff --git a/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/Timelines.java b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/Timelines.java new file mode 100644 index 00000000..747fda15 --- /dev/null +++ b/subprojects/viatra-runtime-matchers/src/main/java/tools/refinery/viatra/runtime/matchers/util/timeline/Timelines.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.matchers.util.timeline; + +import java.util.List; + +/** + * Utility class for creating {@link Timeline}s. + * @author Tamas Szabo + * @since 2.4 + */ +public final class Timelines> { + + public static > Timeline createEmpty() { + return new CompactTimeline(); + } + + public static > Timeline createFrom( + final Diff diffs) { + if (diffs.size() == 1) { + return new SingletonTimeline(diffs); + } else { + return new CompactTimeline(diffs); + } + } + + public static > Timeline createFrom( + final List timestamps) { + if (timestamps.size() == 1) { + return new SingletonTimeline(timestamps.get(0)); + } else { + return new CompactTimeline(timestamps); + } + } + + public static > Timeline createFrom(final Timestamp timestamp) { + return new SingletonTimeline(timestamp); + } + +} diff --git a/subprojects/viatra-runtime-rete-recipes/META-INF/MANIFEST.MF b/subprojects/viatra-runtime-rete-recipes/META-INF/MANIFEST.MF new file mode 100644 index 00000000..c732af46 --- /dev/null +++ b/subprojects/viatra-runtime-rete-recipes/META-INF/MANIFEST.MF @@ -0,0 +1,18 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: %pluginName +Bundle-SymbolicName: viatra-runtime-rete-recipes;singleton:=true +Automatic-Module-Name: viatra-runtime-rete-recipes +Bundle-Version: 1.0.0.qualifier +Bundle-ClassPath: viatra-runtime-rete-recipes.jar +Bundle-Vendor: %providerName +Bundle-Localization: plugin +Bundle-RequiredExecutionEnvironment: JavaSE-1.7 +Export-Package: org.eclipse.viatra.query.runtime.rete.recipes, + org.eclipse.viatra.query.runtime.rete.recipes.impl, + org.eclipse.viatra.query.runtime.rete.recipes.util +Require-Bundle: org.eclipse.core.runtime, + org.eclipse.emf.ecore;visibility:=reexport, + org.eclipse.xtext.xbase.lib, + org.eclipse.emf.ecore.xcore.lib +Bundle-ActivationPolicy: lazy diff --git a/subprojects/viatra-runtime-rete-recipes/META-INF/MANIFEST.MF.license b/subprojects/viatra-runtime-rete-recipes/META-INF/MANIFEST.MF.license new file mode 100644 index 00000000..03d1d42b --- /dev/null +++ b/subprojects/viatra-runtime-rete-recipes/META-INF/MANIFEST.MF.license @@ -0,0 +1,4 @@ +Copyright (c) 2004-2014 Gabor Bergmann and Daniel Varro +Copyright (c) 2023 The Refinery Authors + +SPDX-License-Identifier: EPL-2.0 diff --git a/subprojects/viatra-runtime-rete-recipes/about.html b/subprojects/viatra-runtime-rete-recipes/about.html new file mode 100644 index 00000000..d1d5593a --- /dev/null +++ b/subprojects/viatra-runtime-rete-recipes/about.html @@ -0,0 +1,26 @@ + + + + +About + + + +

    About This Content

    + +

    March 18, 2019

    +

    License

    + +

    The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise indicated below, the Content is provided to you under the terms and conditions of the +Eclipse Public License Version 2.0 ("EPL"). A copy of the EPL is available at http://www.eclipse.org/legal/epl-v20.html. +For purposes of the EPL, "Program" will mean the Content.

    + +

    If you did not receive this Content directly from the Eclipse Foundation, the Content is being redistributed by another party ("Redistributor") and different terms and conditions may +apply to your use of any object code in the Content. Check the Redistributor's license that was provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise +indicated below, the terms and conditions of the EPL still apply to any source code in the Content and such source code may be obtained at http://www.eclipse.org.

    + + diff --git a/subprojects/viatra-runtime-rete-recipes/build.gradle.kts b/subprojects/viatra-runtime-rete-recipes/build.gradle.kts new file mode 100644 index 00000000..1f33e2f9 --- /dev/null +++ b/subprojects/viatra-runtime-rete-recipes/build.gradle.kts @@ -0,0 +1,62 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import tools.refinery.gradle.utils.SonarPropertiesUtils + +plugins { + id("tools.refinery.gradle.java-library") + id("tools.refinery.gradle.mwe2") + id("tools.refinery.gradle.sonarqube") +} + +dependencies { + api(project(":refinery-viatra-runtime-matchers")) + api(libs.ecore) + mwe2(libs.ecore.codegen) + mwe2(libs.mwe.utils) + mwe2(libs.mwe2.lib) + mwe2(libs.slf4j.simple) + mwe2(libs.xtext.core) + mwe2(libs.xtext.xbase) +} + +sourceSets { + main { + java.srcDir("src/main/emf-gen") + } +} + +tasks { + val generateEPackage by registering(JavaExec::class) { + mainClass.set("org.eclipse.emf.mwe2.launch.runtime.Mwe2Launcher") + classpath(configurations.mwe2) + inputs.file("src/main/java/tools/refinery/viatra/runtime/rete/recipes/GenerateReteRecipes.mwe2") + inputs.file("src/main/resources/model/recipes.ecore") + inputs.file("src/main/resources/model/rete-recipes.genmodel") + outputs.file("build.properties") + outputs.file("META-INF/MANIFEST.MF") + outputs.file("plugin.xml") + outputs.file("plugin.properties") + outputs.dir("src/main/emf-gen") + args("src/main/java/tools/refinery/viatra/runtime/rete/recipes/GenerateReteRecipes.mwe2", + "-p", "rootPath=/$projectDir") + } + + for (taskName in listOf("compileJava", "processResources", "generateEclipseSourceFolders")) { + named(taskName) { + dependsOn(generateEPackage) + } + } + + clean { + delete("src/main/emf-gen") + } +} + +sonarqube.properties { + SonarPropertiesUtils.addToList(properties, "sonar.exclusions", "src/main/emf-gen/**") +} + diff --git a/subprojects/viatra-runtime-rete-recipes/build.properties b/subprojects/viatra-runtime-rete-recipes/build.properties new file mode 100644 index 00000000..97e4d6bd --- /dev/null +++ b/subprojects/viatra-runtime-rete-recipes/build.properties @@ -0,0 +1,15 @@ +# Copyright (c) 2004-2014 Gabor Bergmann and Daniel Varro +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-v20.html. +# +# SPDX-License-Identifier: EPL-2.0 + +bin.includes = viatra-runtime-rete-recipes.jar,\ + model/,\ + META-INF/,\ + plugin.xml,\ + plugin.properties +jars.compile.order = viatra-runtime-rete-recipes.jar +source.viatra-runtime-rete-recipes.jar = src-gen/ +output.viatra-runtime-rete-recipes.jar = bin/ diff --git a/subprojects/viatra-runtime-rete-recipes/plugin.properties b/subprojects/viatra-runtime-rete-recipes/plugin.properties new file mode 100644 index 00000000..43a7236d --- /dev/null +++ b/subprojects/viatra-runtime-rete-recipes/plugin.properties @@ -0,0 +1,9 @@ +# Copyright (c) 2004-2014 Gabor Bergmann and Daniel Varro +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-v20.html. +# +# SPDX-License-Identifier: EPL-2.0 + +pluginName = Rete-recipes Model +providerName = www.example.org diff --git a/subprojects/viatra-runtime-rete-recipes/plugin.xml b/subprojects/viatra-runtime-rete-recipes/plugin.xml new file mode 100644 index 00000000..e4917f5d --- /dev/null +++ b/subprojects/viatra-runtime-rete-recipes/plugin.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + diff --git a/subprojects/viatra-runtime-rete-recipes/src/main/java/tools/refinery/viatra/runtime/rete/recipes/GenerateReteRecipes.mwe2 b/subprojects/viatra-runtime-rete-recipes/src/main/java/tools/refinery/viatra/runtime/rete/recipes/GenerateReteRecipes.mwe2 new file mode 100644 index 00000000..424cc9f5 --- /dev/null +++ b/subprojects/viatra-runtime-rete-recipes/src/main/java/tools/refinery/viatra/runtime/rete/recipes/GenerateReteRecipes.mwe2 @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +module tools.refinery.viatra.runtime.rete.recipes.GenerateReteRecipes + +Workflow { + bean = org.eclipse.emf.mwe.utils.StandaloneSetup { + projectMapping = { + projectName = "tools.refinery.refinery-viatra-runtime-rete-recipes" + path = "." + } + } + + component = org.eclipse.emf.mwe.utils.DirectoryCleaner { + directory = "src/main/emf-gen" + } + + component = org.eclipse.emf.mwe2.ecore.EcoreGenerator { + generateCustomClasses = false + genModel = "platform:/resource/tools.refinery.refinery-viatra-runtime-rete-recipes/src/main/resources/model/rete-recipes.genmodel" + srcPath = "platform:/resource/tools.refinery.refinery-viatra-runtime-rete-recipes/src/main/emf-gen" + } +} diff --git a/subprojects/viatra-runtime-rete-recipes/src/main/java/tools/refinery/viatra/runtime/rete/recipes/helper/RecipeRecognizer.java b/subprojects/viatra-runtime-rete-recipes/src/main/java/tools/refinery/viatra/runtime/rete/recipes/helper/RecipeRecognizer.java new file mode 100644 index 00000000..2c252593 --- /dev/null +++ b/subprojects/viatra-runtime-rete-recipes/src/main/java/tools/refinery/viatra/runtime/rete/recipes/helper/RecipeRecognizer.java @@ -0,0 +1,204 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Gabor Bergmann, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.recipes.helper; + +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.EAttribute; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.emf.ecore.util.EcoreUtil; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.rete.recipes.RecipesPackage; +import tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe; + +import java.util.*; + +/** + * Stores a set of known canonical recipes, each representing a disjoint equivalence class of recipes, modulo + * {@link #isEquivalentRecipe(ReteNodeRecipe, ReteNodeRecipe)}. + * + * @author Gabor Bergmann + * @since 1.3 + * + */ +public class RecipeRecognizer { + private static long nextRecipeEquivalenceClassID = 0; + + /** + * if EcoreUtil.equals(recipe1, recipe2), only one of them will be included here + */ + Map> canonicalRecipesByClass = new HashMap<>(); + Map canonicalRecipeByEquivalenceClassID = new HashMap<>(); + + private IQueryRuntimeContext runtimeContext; + + /** + * @param can be null; if provided, further equivalences can be detected based on {@link IQueryRuntimeContext#wrapElement(Object)} + * @since 1.6 + */ + public RecipeRecognizer(IQueryRuntimeContext runtimeContext) { + this.runtimeContext = runtimeContext; + } + public RecipeRecognizer() { + this(null); + } + + /** + * Recognizes when an equivalent canonical recipe is already known. + * + * @return an equivalent canonical recipe, or the null if no known equivalent found + */ + public ReteNodeRecipe peekCanonicalRecipe(final ReteNodeRecipe recipe) { + // equivalence class already known + for (Long classID : recipe.getEquivalenceClassIDs()) { + ReteNodeRecipe knownRecipe = canonicalRecipeByEquivalenceClassID.get(classID); + if (knownRecipe != null) + return knownRecipe; + } + + // equivalence class not known, but maybe equivalent recipe still + // available + Collection sameClassRecipes = getSameClassCanonicalRecipes(recipe); + for (ReteNodeRecipe knownRecipe : sameClassRecipes) { + if (isEquivalentRecipe(recipe, knownRecipe)) { + // FOUND EQUIVALENT RECIPE + recipe.getEquivalenceClassIDs().add(knownRecipe.getEquivalenceClassIDs().get(0)); + return knownRecipe; + } + } + + return null; + } + + /** + * This recipe will be remembered as a canonical recipe. Method maintains both internal data structures and the + * equivalence class attribute of the recipe. PRECONDITION: {@link #peekCanonicalRecipe(ReteNodeRecipe)} must return + * null or the recipe itself + */ + public void makeCanonical(final ReteNodeRecipe recipe) { + // this is a canonical recipe, chosen representative of its new + // equivalence class + if (recipe.getEquivalenceClassIDs().isEmpty()) { + recipe.getEquivalenceClassIDs().add(nextRecipeEquivalenceClassID++); + } + for (Long classID : recipe.getEquivalenceClassIDs()) { + canonicalRecipeByEquivalenceClassID.put(classID, recipe); + } + getSameClassCanonicalRecipes(recipe).add(recipe); + } + + /** + * Ensures that there is an equivalent canonical recipe; if none is known yet, this recipe will be remembered as + * canonical. + * + * @return an equivalent canonical recipe; the argument recipe itself (which is made canonical) if no known + * equivalent found + */ + public ReteNodeRecipe canonicalizeRecipe(final ReteNodeRecipe recipe) { + ReteNodeRecipe knownRecipe = peekCanonicalRecipe(recipe); + if (knownRecipe == null) { + knownRecipe = recipe; + makeCanonical(recipe); + } + return knownRecipe; + } + + /** + * @return true iff recipe is a canonical recipe + */ + public boolean isKnownCanonicalRecipe(final ReteNodeRecipe recipe) { + if (recipe.getEquivalenceClassIDs().isEmpty()) { + return false; + } + ReteNodeRecipe knownRecipe = canonicalRecipeByEquivalenceClassID.get(recipe.getEquivalenceClassIDs().get(0)); + return recipe == knownRecipe; + } + + private Set getSameClassCanonicalRecipes(final ReteNodeRecipe recipe) { + Set sameClassRecipes = canonicalRecipesByClass.get(recipe.eClass()); + if (sameClassRecipes == null) { + sameClassRecipes = new HashSet<>(); + canonicalRecipesByClass.put(recipe.eClass(), sameClassRecipes); + } + return sameClassRecipes; + } + + private boolean isEquivalentRecipe(ReteNodeRecipe recipe, ReteNodeRecipe knownRecipe) { + return new EqualityHelper(runtimeContext).equals(recipe, knownRecipe); + } + + // TODO reuse in more cases later, e.g. switching join node parents, etc. + private static class EqualityHelper extends EcoreUtil.EqualityHelper { + + + + + private static final long serialVersionUID = -8841971394686015188L; + + private static final EAttribute RETE_NODE_RECIPE_EQUIVALENCE_CLASS_IDS = + RecipesPackage.eINSTANCE.getReteNodeRecipe_EquivalenceClassIDs(); + private static final EAttribute CONSTANT_RECIPE_CONSTANT_VALUES = + RecipesPackage.eINSTANCE.getConstantRecipe_ConstantValues(); + private static final EAttribute DISCRIMINATOR_BUCKET_RECIPE_BUCKET_KEY = + RecipesPackage.eINSTANCE.getDiscriminatorBucketRecipe_BucketKey(); + + private IQueryRuntimeContext runtimeContext; + + public EqualityHelper(IQueryRuntimeContext runtimeContext) { + this.runtimeContext = runtimeContext; + } + + @Override + protected boolean haveEqualFeature(EObject eObject1, EObject eObject2, EStructuralFeature feature) { + // ignore differences in this attribute, as it may only be assigned + // after the equivalence check + if (RETE_NODE_RECIPE_EQUIVALENCE_CLASS_IDS.equals(feature)) + return true; + + if (runtimeContext != null) { + // constant values + if (/*CONSTANT_RECIPE_CONSTANT_VALUES.equals(feature) ||*/ DISCRIMINATOR_BUCKET_RECIPE_BUCKET_KEY.equals(feature)) { + // use runtime context to map to canonical wrapped form + // this is costly for constant recipes (TODO improve this), but essential for discriminator buckets + + Object val1 = eObject1.eGet(feature); + Object val2 = eObject2.eGet(feature); + + if (val1 != null && val2 != null) { + return runtimeContext.wrapElement(val1).equals(runtimeContext.wrapElement(val2)); + } else { + return val1 == null && val2 == null; + } + + } + } + + // fallback to general comparison + return super.haveEqualFeature(eObject1, eObject2, feature); + } + + @Override + public boolean equals(EObject eObject1, EObject eObject2) { + // short-circuit if already known to be equivalent + if (eObject1 instanceof ReteNodeRecipe) { + if (eObject2 instanceof ReteNodeRecipe) { + EList eqClassIDs1 = ((ReteNodeRecipe) eObject1).getEquivalenceClassIDs(); + EList eqClassIDs2 = ((ReteNodeRecipe) eObject2).getEquivalenceClassIDs(); + + if (!Collections.disjoint(eqClassIDs1, eqClassIDs2)) + return true; + } + } + + // fallback to general comparison + return super.equals(eObject1, eObject2); + } + } +} diff --git a/subprojects/viatra-runtime-rete-recipes/src/main/java/tools/refinery/viatra/runtime/rete/recipes/helper/RecipesHelper.java b/subprojects/viatra-runtime-rete-recipes/src/main/java/tools/refinery/viatra/runtime/rete/recipes/helper/RecipesHelper.java new file mode 100644 index 00000000..3070fcf5 --- /dev/null +++ b/subprojects/viatra-runtime-rete-recipes/src/main/java/tools/refinery/viatra/runtime/rete/recipes/helper/RecipesHelper.java @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2004-2014 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.viatra.runtime.rete.recipes.helper; + +import org.eclipse.emf.common.util.EList; +import tools.refinery.viatra.runtime.rete.recipes.*; + +import java.util.Collection; + +/** + * Static helper class for easy construction of recipes. + * + * @author Bergmann Gabor + */ +public class RecipesHelper { + private static final RecipesFactory FACTORY = RecipesFactory.eINSTANCE; + + private RecipesHelper() {/* Utility class constructor */} + + /** + * @since 2.0 + */ + public static Mask mask(final int sourceArity, final Collection sourceIndices) { + Mask mask = RecipesHelper.FACTORY.createMask(); + mask.setSourceArity(sourceArity); + mask.getSourceIndices().addAll(sourceIndices); + return mask; + } + + public static Mask mask(final int sourceArity, final int... sourceIndices) { + Mask mask = RecipesHelper.FACTORY.createMask(); + mask.setSourceArity(sourceArity); + final EList maskIndeces = mask.getSourceIndices(); + for (int index : sourceIndices) { + maskIndeces.add(index); + } + return mask; + } + + public static ProjectionIndexerRecipe projectionIndexerRecipe(final ReteNodeRecipe parent, final Mask mask) { + ProjectionIndexerRecipe recipe = RecipesHelper.FACTORY.createProjectionIndexerRecipe(); + recipe.setParent(parent); + recipe.setMask(mask); + return recipe; + } + + public static ExpressionDefinition expressionDefinition(final Object evaluator) { + ExpressionDefinition definition = RecipesHelper.FACTORY.createExpressionDefinition(); + definition.setEvaluator(evaluator); + return definition; + } + + public static InputRecipe inputRecipe(final Object inputKey, final String inputKeyID, final int arity) { + InputRecipe recipe = RecipesHelper.FACTORY.createInputRecipe(); + recipe.setInputKey(inputKey); + recipe.setKeyArity(arity); + recipe.setKeyID(inputKeyID); + recipe.setTraceInfo(inputKeyID); + return recipe; + } + + /** + * Mask can be null in case no tuple reordering or trimming is needed + */ + public static InputFilterRecipe inputFilterRecipe(final ReteNodeRecipe parent, final Object inputKey, + final String inputKeyID, final Mask mask) { + InputFilterRecipe it = RecipesHelper.FACTORY.createInputFilterRecipe(); + it.setParent(parent); + it.setInputKey(inputKey); + it.setKeyID(inputKeyID); + it.setTraceInfo(inputKeyID); + it.setMask(mask); + return it; + } +} diff --git a/subprojects/viatra-runtime-rete-recipes/src/main/resources/model/recipes.ecore b/subprojects/viatra-runtime-rete-recipes/src/main/resources/model/recipes.ecore new file mode 100644 index 00000000..4eda701e --- /dev/null +++ b/subprojects/viatra-runtime-rete-recipes/src/main/resources/model/recipes.ecore @@ -0,0 +1,400 @@ + + + + +
    + + + + + +
    + + + +
    +
    + + + + +
    + + + + +
    + + + + + +
    + + + + + +
    + + + + + +
    + + + + + + + + + + + +
    + + + + +
    + + + +
    + + + + +
    + + + + +
    + + + + + + +
    + + + +
    + + + + + + +
    + + + + +
    + + + + +
    + + + + +
    + + + + + +
    + + + +
    + + + + +
    + + + + + +
    + + + + + + + + +
    + + + +
    + + + + + +
    + + + + + + +
    + + + +
    + + + + +
    + + + + + +
    + + + +
    + + + + + +
    + + + +
    + + + + + + + + + + + + + +
    + + + +
    + + + + + + + + + +
    + + + +
    + + + + +
    + + + + + + +
    + + + +
    + + + + + +
    + + + +
    + + + + +
    + + + + + +
    + + + +
    + + + + + + +
    + + + + +
    + + + +
    + + + + + + +
    + + + +
    + + + + + +
    + + + + +
    + + + + + + +
    + + + + + + + +
    + + + + + + + + + +
    + + + +
    + + + + + + +
    + + + +
    + + + + + + + + + + +
    + + + + + +
    + + + +
    + + + + + + diff --git a/subprojects/viatra-runtime-rete-recipes/src/main/resources/model/recipes.ecore.license b/subprojects/viatra-runtime-rete-recipes/src/main/resources/model/recipes.ecore.license new file mode 100644 index 00000000..03d1d42b --- /dev/null +++ b/subprojects/viatra-runtime-rete-recipes/src/main/resources/model/recipes.ecore.license @@ -0,0 +1,4 @@ +Copyright (c) 2004-2014 Gabor Bergmann and Daniel Varro +Copyright (c) 2023 The Refinery Authors + +SPDX-License-Identifier: EPL-2.0 diff --git a/subprojects/viatra-runtime-rete-recipes/src/main/resources/model/rete-recipes.genmodel b/subprojects/viatra-runtime-rete-recipes/src/main/resources/model/rete-recipes.genmodel new file mode 100644 index 00000000..6b44fde6 --- /dev/null +++ b/subprojects/viatra-runtime-rete-recipes/src/main/resources/model/rete-recipes.genmodel @@ -0,0 +1,171 @@ + + + + +
    + +
    + + + +
    + +
    + + recipes.ecore + org.eclipse.xtext.xbase.lib + org.eclipse.emf.ecore.xcore.lib + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/subprojects/viatra-runtime-rete-recipes/src/main/resources/model/rete-recipes.genmodel.license b/subprojects/viatra-runtime-rete-recipes/src/main/resources/model/rete-recipes.genmodel.license new file mode 100644 index 00000000..03d1d42b --- /dev/null +++ b/subprojects/viatra-runtime-rete-recipes/src/main/resources/model/rete-recipes.genmodel.license @@ -0,0 +1,4 @@ +Copyright (c) 2004-2014 Gabor Bergmann and Daniel Varro +Copyright (c) 2023 The Refinery Authors + +SPDX-License-Identifier: EPL-2.0 diff --git a/subprojects/viatra-runtime-rete/about.html b/subprojects/viatra-runtime-rete/about.html new file mode 100644 index 00000000..d1d5593a --- /dev/null +++ b/subprojects/viatra-runtime-rete/about.html @@ -0,0 +1,26 @@ + + + + +About + + + +

    About This Content

    + +

    March 18, 2019

    +

    License

    + +

    The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise indicated below, the Content is provided to you under the terms and conditions of the +Eclipse Public License Version 2.0 ("EPL"). A copy of the EPL is available at http://www.eclipse.org/legal/epl-v20.html. +For purposes of the EPL, "Program" will mean the Content.

    + +

    If you did not receive this Content directly from the Eclipse Foundation, the Content is being redistributed by another party ("Redistributor") and different terms and conditions may +apply to your use of any object code in the Content. Check the Redistributor's license that was provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise +indicated below, the terms and conditions of the EPL still apply to any source code in the Content and such source code may be obtained at http://www.eclipse.org.

    + + diff --git a/subprojects/viatra-runtime-rete/build.gradle.kts b/subprojects/viatra-runtime-rete/build.gradle.kts new file mode 100644 index 00000000..7e795a90 --- /dev/null +++ b/subprojects/viatra-runtime-rete/build.gradle.kts @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ + +plugins { + id("tools.refinery.gradle.java-library") +} + +dependencies { + implementation(project(":refinery-viatra-runtime")) + implementation(project(":refinery-viatra-runtime-rete-recipes")) + implementation(libs.slf4j.log4j) +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/AbstractColumnAggregatorNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/AbstractColumnAggregatorNode.java new file mode 100644 index 00000000..2588bde1 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/AbstractColumnAggregatorNode.java @@ -0,0 +1,474 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.aggregation; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; + +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.Clearable; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.index.Indexer; +import tools.refinery.viatra.runtime.rete.index.StandardIndexer; +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.single.SingleInputNode; + +/** + * Groups incoming tuples by the given mask, and aggregates values at a specific index in each group. + *

    + * Direct children are not supported, use via outer join indexers instead. + *

    + * There are both timeless and timely implementations. + * + * @author Tamas Szabo + * @since 2.2 + * + */ +public abstract class AbstractColumnAggregatorNode extends SingleInputNode + implements Clearable, IAggregatorNode { + + /** + * @since 1.6 + */ + protected final IMultisetAggregationOperator operator; + + /** + * @since 1.6 + */ + protected final TupleMask groupMask; + + /** + * @since 1.6 + */ + protected final TupleMask columnMask; + + /** + * @since 1.6 + */ + protected final int sourceWidth; + + /** + * @since 1.6 + */ + protected final IQueryRuntimeContext runtimeContext; + + protected final AggregateResult NEUTRAL; + + protected AggregatorOuterIndexer aggregatorOuterIndexer; + + @SuppressWarnings("rawtypes") + protected AbstractColumnAggregatorNode.AggregatorOuterIdentityIndexer[] aggregatorOuterIdentityIndexers; + + /** + * Creates a new column aggregator node. + * + * @param reteContainer + * the RETE container of the node + * @param operator + * the aggregation operator + * @param deleteRederiveEvaluation + * true if the node should run in DRED mode, false otherwise + * @param groupMask + * the mask that masks a tuple to obtain the key that we are grouping-by + * @param columnMask + * the mask that masks a tuple to obtain the tuple element(s) that we are aggregating over + * @param posetComparator + * the poset comparator for the column, if known, otherwise it can be null + * @since 1.6 + */ + public AbstractColumnAggregatorNode(final ReteContainer reteContainer, + final IMultisetAggregationOperator operator, + final TupleMask groupMask, final TupleMask columnMask) { + super(reteContainer); + this.operator = operator; + this.groupMask = groupMask; + this.columnMask = columnMask; + this.sourceWidth = groupMask.indices.length; + this.runtimeContext = reteContainer.getNetwork().getEngine().getRuntimeContext(); + this.NEUTRAL = operator.getAggregate(operator.createNeutral()); + reteContainer.registerClearable(this); + } + + /** + * Creates a new column aggregator node. + * + * @param reteContainer + * the RETE container of the node + * @param operator + * the aggregation operator + * @param groupMask + * the mask that masks a tuple to obtain the key that we are grouping-by + * @param aggregatedColumn + * the index of the column that the aggregator node is aggregating over + */ + public AbstractColumnAggregatorNode(final ReteContainer reteContainer, + final IMultisetAggregationOperator operator, + final TupleMask groupMask, final int aggregatedColumn) { + this(reteContainer, operator, groupMask, TupleMask.selectSingle(aggregatedColumn, groupMask.sourceWidth)); + } + + @Override + public CommunicationTracker getCommunicationTracker() { + return this.reteContainer.getCommunicationTracker(); + } + + @Override + public void pullInto(Collection collector, boolean flush) { + // DIRECT CHILDREN NOT SUPPORTED + throw new UnsupportedOperationException(); + } + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + // DIRECT CHILDREN NOT SUPPORTED + throw new UnsupportedOperationException(); + } + + @Override + public void appendChild(Receiver receiver) { + // DIRECT CHILDREN NOT SUPPORTED + throw new UnsupportedOperationException(); + } + + @Override + public Indexer getAggregatorOuterIndexer() { + if (aggregatorOuterIndexer == null) { + aggregatorOuterIndexer = new AggregatorOuterIndexer(); + this.getCommunicationTracker().registerDependency(this, aggregatorOuterIndexer); + } + return aggregatorOuterIndexer; + } + + @Override + public Indexer getAggregatorOuterIdentityIndexer(final int resultPositionInSignature) { + if (aggregatorOuterIdentityIndexers == null) { + aggregatorOuterIdentityIndexers = new AbstractColumnAggregatorNode.AggregatorOuterIdentityIndexer[sourceWidth + + 1]; + } + if (aggregatorOuterIdentityIndexers[resultPositionInSignature] == null) { + aggregatorOuterIdentityIndexers[resultPositionInSignature] = new AggregatorOuterIdentityIndexer( + resultPositionInSignature); + this.getCommunicationTracker().registerDependency(this, + aggregatorOuterIdentityIndexers[resultPositionInSignature]); + } + return aggregatorOuterIdentityIndexers[resultPositionInSignature]; + } + + /** + * @since 2.4 + */ + public void propagateAggregateResultUpdate(final Tuple group, final AggregateResult oldValue, + final AggregateResult newValue, final Timestamp timestamp) { + if (!Objects.equals(oldValue, newValue)) { + propagate(Direction.DELETE, group, oldValue, timestamp); + propagate(Direction.INSERT, group, newValue, timestamp); + } + } + + /** + * @since 2.4 + */ + @SuppressWarnings("unchecked") + public void propagate(final Direction direction, final Tuple group, final AggregateResult value, + final Timestamp timestamp) { + final Tuple tuple = tupleFromAggregateResult(group, value); + + if (aggregatorOuterIndexer != null) { + aggregatorOuterIndexer.propagate(direction, tuple, group, timestamp); + } + if (aggregatorOuterIdentityIndexers != null) { + for (final AggregatorOuterIdentityIndexer aggregatorOuterIdentityIndexer : aggregatorOuterIdentityIndexers) { + if (aggregatorOuterIdentityIndexer != null) { + aggregatorOuterIdentityIndexer.propagate(direction, tuple, group, timestamp); + } + } + } + } + + public abstract Tuple getAggregateTuple(final Tuple key); + + /** + * @since 2.4 + */ + public abstract Map> getAggregateTupleTimeline(final Tuple key); + + public abstract AggregateResult getAggregateResult(final Tuple key); + + /** + * @since 2.4 + */ + public abstract Map> getAggregateResultTimeline(final Tuple key); + + protected Tuple tupleFromAggregateResult(final Tuple groupTuple, final AggregateResult aggregateResult) { + if (aggregateResult == null) { + return null; + } else { + return Tuples.staticArityLeftInheritanceTupleOf(groupTuple, runtimeContext.wrapElement(aggregateResult)); + } + } + + /** + * A special non-iterable index that retrieves the aggregated, packed result (signature+aggregate) for the original + * signature. + * + * @author Gabor Bergmann + * @author Tamas Szabo + * + */ + protected class AggregatorOuterIndexer extends StandardIndexer { + + /** + * @since 2.4 + */ + protected NetworkStructureChangeSensitiveLogic logic; + + public AggregatorOuterIndexer() { + super(AbstractColumnAggregatorNode.this.reteContainer, TupleMask.omit(sourceWidth, sourceWidth + 1)); + this.parent = AbstractColumnAggregatorNode.this; + this.logic = createLogic(); + } + + @Override + public void networkStructureChanged() { + super.networkStructureChanged(); + this.logic = createLogic(); + } + + @Override + public Collection get(final Tuple signature) { + return this.logic.get(signature); + } + + @Override + public Map> getTimeline(final Tuple signature) { + return this.logic.getTimeline(signature); + } + + /** + * @since 2.4 + */ + public void propagate(final Direction direction, final Tuple tuple, final Tuple group, + final Timestamp timestamp) { + if (tuple != null) { + propagate(direction, tuple, group, true, timestamp); + } + } + + @Override + public Node getActiveNode() { + return AbstractColumnAggregatorNode.this; + } + + /** + * @since 2.4 + */ + protected NetworkStructureChangeSensitiveLogic createLogic() { + if (this.reteContainer.isTimelyEvaluation() + && this.reteContainer.getCommunicationTracker().isInRecursiveGroup(this)) { + return this.TIMELY; + } else { + return this.TIMELESS; + } + } + + private final NetworkStructureChangeSensitiveLogic TIMELESS = new NetworkStructureChangeSensitiveLogic() { + + @Override + public Collection get(final Tuple signature) { + final Tuple aggregateTuple = getAggregateTuple(signature); + if (aggregateTuple == null) { + return null; + } else { + return Collections.singleton(aggregateTuple); + } + } + + @Override + public Map> getTimeline(final Tuple signature) { + throw new UnsupportedOperationException(); + } + + }; + + private final NetworkStructureChangeSensitiveLogic TIMELY = new NetworkStructureChangeSensitiveLogic() { + + @Override + public Collection get(final Tuple signatureWithResult) { + return TIMELESS.get(signatureWithResult); + } + + @Override + public Map> getTimeline(final Tuple signature) { + final Map> aggregateTuples = getAggregateTupleTimeline(signature); + if (aggregateTuples.isEmpty()) { + return null; + } else { + return aggregateTuples; + } + } + + }; + + } + + /** + * A special non-iterable index that checks a suspected aggregate value for a given signature. The signature for + * this index is the original 'group by' masked tuple, with the suspected result inserted at position + * resultPositionInSignature. + * + * @author Gabor Bergmann + * @author Tamas Szabo + * + */ + protected class AggregatorOuterIdentityIndexer extends StandardIndexer { + + protected final int resultPositionInSignature; + protected final TupleMask pruneResult; + protected final TupleMask reorderMask; + /** + * @since 2.4 + */ + protected NetworkStructureChangeSensitiveLogic logic; + + public AggregatorOuterIdentityIndexer(final int resultPositionInSignature) { + super(AbstractColumnAggregatorNode.this.reteContainer, + TupleMask.displace(sourceWidth, resultPositionInSignature, sourceWidth + 1)); + this.resultPositionInSignature = resultPositionInSignature; + this.pruneResult = TupleMask.omit(resultPositionInSignature, sourceWidth + 1); + if (resultPositionInSignature == sourceWidth) { + this.reorderMask = null; + } else { + this.reorderMask = mask; + } + this.logic = createLogic(); + } + + @Override + public void networkStructureChanged() { + super.networkStructureChanged(); + this.logic = createLogic(); + } + + @Override + public Collection get(final Tuple signatureWithResult) { + return this.logic.get(signatureWithResult); + } + + /** + * @since 2.4 + */ + @Override + public Map> getTimeline(final Tuple signature) { + return this.logic.getTimeline(signature); + } + + /** + * @since 2.4 + */ + public void propagate(final Direction direction, final Tuple tuple, final Tuple group, + final Timestamp timestamp) { + if (tuple != null) { + propagate(direction, reorder(tuple), group, true, timestamp); + } + } + + private Tuple reorder(final Tuple signatureWithResult) { + Tuple transformed; + if (reorderMask == null) { + transformed = signatureWithResult; + } else { + transformed = reorderMask.transform(signatureWithResult); + } + return transformed; + } + + @Override + public Node getActiveNode() { + return this.parent; + } + + /** + * @since 2.4 + */ + protected NetworkStructureChangeSensitiveLogic createLogic() { + if (this.reteContainer.isTimelyEvaluation() + && this.reteContainer.getCommunicationTracker().isInRecursiveGroup(this)) { + return this.TIMELY; + } else { + return this.TIMELESS; + } + } + + private final NetworkStructureChangeSensitiveLogic TIMELESS = new NetworkStructureChangeSensitiveLogic() { + + @Override + public Collection get(final Tuple signatureWithResult) { + final Tuple prunedSignature = pruneResult.transform(signatureWithResult); + final AggregateResult result = getAggregateResult(prunedSignature); + if (result != null && Objects.equals(signatureWithResult.get(resultPositionInSignature), result)) { + return Collections.singleton(signatureWithResult); + } else { + return null; + } + } + + @Override + public Map> getTimeline(final Tuple signature) { + throw new UnsupportedOperationException(); + } + + }; + + private final NetworkStructureChangeSensitiveLogic TIMELY = new NetworkStructureChangeSensitiveLogic() { + + @Override + public Collection get(final Tuple signatureWithResult) { + return TIMELESS.get(signatureWithResult); + } + + @Override + public Map> getTimeline(final Tuple signatureWithResult) { + final Tuple prunedSignature = pruneResult.transform(signatureWithResult); + final Map> result = getAggregateResultTimeline(prunedSignature); + for (final Entry> entry : result.entrySet()) { + if (Objects.equals(signatureWithResult.get(resultPositionInSignature), entry.getKey())) { + return Collections.singletonMap(signatureWithResult, entry.getValue()); + } + } + return null; + } + + }; + + } + + /** + * @since 2.4 + */ + protected static abstract class NetworkStructureChangeSensitiveLogic { + + public abstract Collection get(final Tuple signatureWithResult); + + public abstract Map> getTimeline(final Tuple signature); + + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/ColumnAggregatorNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/ColumnAggregatorNode.java new file mode 100644 index 00000000..4480aed8 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/ColumnAggregatorNode.java @@ -0,0 +1,369 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.aggregation; + +import java.util.Map; +import java.util.Map.Entry; + +import tools.refinery.viatra.runtime.matchers.context.IPosetComparator; +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.PosetAwareReceiver; +import tools.refinery.viatra.runtime.rete.network.RederivableNode; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.communication.timeless.RecursiveCommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.timeless.BehaviorChangingMailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.timeless.PosetAwareMailbox; + +/** + * Timeless implementation of the column aggregator node. + *

    + * The node is capable of operating in the delete and re-derive mode. In this mode, it is also possible to equip the + * node with an {@link IPosetComparator} to identify monotone changes; thus, ensuring that a fix-point can be reached + * during the evaluation. + * + * @author Gabor Bergmann + * @author Tamas Szabo + * @since 1.4 + */ +public class ColumnAggregatorNode + extends AbstractColumnAggregatorNode + implements RederivableNode, PosetAwareReceiver { + + /** + * @since 1.6 + */ + protected final IPosetComparator posetComparator; + + /** + * @since 1.6 + */ + protected final boolean deleteRederiveEvaluation; + + // invariant: neutral values are not stored + /** + * @since 1.6 + */ + protected final Map memory; + /** + * @since 1.6 + */ + protected final Map rederivableMemory; + + /** + * @since 1.7 + */ + protected CommunicationGroup currentGroup; + + /** + * Creates a new column aggregator node. + * + * @param reteContainer + * the RETE container of the node + * @param operator + * the aggregation operator + * @param deleteRederiveEvaluation + * true if the node should run in DRED mode, false otherwise + * @param groupMask + * the mask that masks a tuple to obtain the key that we are grouping-by + * @param columnMask + * the mask that masks a tuple to obtain the tuple element(s) that we are aggregating over + * @param posetComparator + * the poset comparator for the column, if known, otherwise it can be null + * @since 1.6 + */ + public ColumnAggregatorNode(final ReteContainer reteContainer, + final IMultisetAggregationOperator operator, + final boolean deleteRederiveEvaluation, final TupleMask groupMask, final TupleMask columnMask, + final IPosetComparator posetComparator) { + super(reteContainer, operator, groupMask, columnMask); + this.memory = CollectionsFactory.createMap(); + this.rederivableMemory = CollectionsFactory.createMap(); + this.deleteRederiveEvaluation = deleteRederiveEvaluation; + this.posetComparator = posetComparator; + // mailbox MUST be instantiated after the fields are all set + this.mailbox = instantiateMailbox(); + } + + /** + * Creates a new column aggregator node. + * + * @param reteContainer + * the RETE container of the node + * @param operator + * the aggregation operator + * @param groupMask + * the mask that masks a tuple to obtain the key that we are grouping-by + * @param aggregatedColumn + * the index of the column that the aggregator node is aggregating over + */ + public ColumnAggregatorNode(final ReteContainer reteContainer, + final IMultisetAggregationOperator operator, + final TupleMask groupMask, final int aggregatedColumn) { + this(reteContainer, operator, false, groupMask, TupleMask.selectSingle(aggregatedColumn, groupMask.sourceWidth), + null); + } + + @Override + public boolean isInDRedMode() { + return this.deleteRederiveEvaluation; + } + + @Override + protected Mailbox instantiateMailbox() { + if (groupMask != null && columnMask != null && posetComparator != null) { + return new PosetAwareMailbox(this, this.reteContainer); + } else { + return new BehaviorChangingMailbox(this, this.reteContainer); + } + } + + @Override + public TupleMask getCoreMask() { + return groupMask; + } + + @Override + public TupleMask getPosetMask() { + return columnMask; + } + + @Override + public IPosetComparator getPosetComparator() { + return posetComparator; + } + + @Override + public void rederiveOne() { + final Entry entry = rederivableMemory.entrySet().iterator().next(); + final Tuple group = entry.getKey(); + final Accumulator accumulator = entry.getValue(); + rederivableMemory.remove(group); + memory.put(group, accumulator); + // unregister the node if there is nothing left to be re-derived + if (this.rederivableMemory.isEmpty()) { + ((RecursiveCommunicationGroup) currentGroup).removeRederivable(this); + } + final AggregateResult value = operator.getAggregate(accumulator); + propagateAggregateResultUpdate(group, NEUTRAL, value, Timestamp.ZERO); + } + + @Override + public void updateWithPosetInfo(final Direction direction, final Tuple update, final boolean monotone) { + if (this.deleteRederiveEvaluation) { + updateWithDeleteAndRederive(direction, update, monotone); + } else { + updateDefault(direction, update, Timestamp.ZERO); + } + } + + @Override + public void update(final Direction direction, final Tuple update, final Timestamp timestamp) { + updateWithPosetInfo(direction, update, false); + } + + /** + * @since 2.4 + */ + protected void updateDefault(final Direction direction, final Tuple update, final Timestamp timestamp) { + final Tuple key = groupMask.transform(update); + final Tuple value = columnMask.transform(update); + @SuppressWarnings("unchecked") + final Domain aggregableValue = (Domain) runtimeContext.unwrapElement(value.get(0)); + final boolean isInsertion = direction == Direction.INSERT; + + final Accumulator oldMainAccumulator = getMainAccumulator(key); + final AggregateResult oldValue = operator.getAggregate(oldMainAccumulator); + + final Accumulator newMainAccumulator = operator.update(oldMainAccumulator, aggregableValue, isInsertion); + storeIfNotNeutral(key, newMainAccumulator, memory); + final AggregateResult newValue = operator.getAggregate(newMainAccumulator); + + propagateAggregateResultUpdate(key, oldValue, newValue, timestamp); + } + + /** + * @since 2.4 + */ + protected void updateWithDeleteAndRederive(final Direction direction, final Tuple update, final boolean monotone) { + final Tuple group = groupMask.transform(update); + final Tuple value = columnMask.transform(update); + @SuppressWarnings("unchecked") + final Domain aggregableValue = (Domain) runtimeContext.unwrapElement(value.get(0)); + final boolean isInsertion = direction == Direction.INSERT; + + Accumulator oldMainAccumulator = memory.get(group); + Accumulator oldRederivableAccumulator = rederivableMemory.get(group); + + if (direction == Direction.INSERT) { + // INSERT + if (oldRederivableAccumulator != null) { + // the group is in the re-derivable memory + final Accumulator newRederivableAccumulator = operator.update(oldRederivableAccumulator, + aggregableValue, isInsertion); + storeIfNotNeutral(group, newRederivableAccumulator, rederivableMemory); + if (rederivableMemory.isEmpty()) { + // there is nothing left to be re-derived + // this can happen if the accumulator became neutral in response to the INSERT + ((RecursiveCommunicationGroup) currentGroup).removeRederivable(this); + } + } else { + // the group is in the main memory + // at this point, it can happen that we need to initialize with a neutral accumulator + if (oldMainAccumulator == null) { + oldMainAccumulator = operator.createNeutral(); + } + + final AggregateResult oldValue = operator.getAggregate(oldMainAccumulator); + final Accumulator newMainAccumulator = operator.update(oldMainAccumulator, aggregableValue, + isInsertion); + storeIfNotNeutral(group, newMainAccumulator, memory); + final AggregateResult newValue = operator.getAggregate(newMainAccumulator); + propagateAggregateResultUpdate(group, oldValue, newValue, Timestamp.ZERO); + } + } else { + // DELETE + if (oldRederivableAccumulator != null) { + // the group is in the re-derivable memory + if (oldMainAccumulator != null) { + issueError("[INTERNAL ERROR] Inconsistent state for " + update + + " because it is present both in the main and re-derivable memory in the ColumnAggregatorNode " + + this + " for pattern(s) " + getTraceInfoPatternsEnumerated(), null); + } + try { + final Accumulator newRederivableAccumulator = operator.update(oldRederivableAccumulator, + aggregableValue, isInsertion); + storeIfNotNeutral(group, newRederivableAccumulator, rederivableMemory); + if (rederivableMemory.isEmpty()) { + // there is nothing left to be re-derived + // this can happen if the accumulator became neutral in response to the DELETE + ((RecursiveCommunicationGroup) currentGroup).removeRederivable(this); + } + } catch (final NullPointerException ex) { + issueError("[INTERNAL ERROR] Deleting a domain element in " + update + + " which did not exist before in ColumnAggregatorNode " + this + " for pattern(s) " + + getTraceInfoPatternsEnumerated(), ex); + } + } else { + // the group is in the main memory + // at this point, it can happen that we need to initialize with a neutral accumulator + if (oldMainAccumulator == null) { + oldMainAccumulator = operator.createNeutral(); + } + + final AggregateResult oldValue = operator.getAggregate(oldMainAccumulator); + final Accumulator newMainAccumulator = operator.update(oldMainAccumulator, aggregableValue, + isInsertion); + final AggregateResult newValue = operator.getAggregate(newMainAccumulator); + + if (monotone) { + storeIfNotNeutral(group, newMainAccumulator, memory); + propagateAggregateResultUpdate(group, oldValue, newValue, Timestamp.ZERO); + } else { + final boolean wasEmpty = rederivableMemory.isEmpty(); + if (storeIfNotNeutral(group, newMainAccumulator, rederivableMemory) && wasEmpty) { + ((RecursiveCommunicationGroup) currentGroup).addRederivable(this); + } + memory.remove(group); + propagateAggregateResultUpdate(group, oldValue, NEUTRAL, Timestamp.ZERO); + } + } + } + } + + @Override + public void clear() { + this.memory.clear(); + this.rederivableMemory.clear(); + this.childMailboxes.clear(); + } + + /** + * Returns true if the accumulator was stored, false otherwise. + * + * @since 1.6 + */ + protected boolean storeIfNotNeutral(final Tuple key, final Accumulator accumulator, + final Map memory) { + if (operator.isNeutral(accumulator)) { + memory.remove(key); + return false; + } else { + memory.put(key, accumulator); + return true; + } + } + + @Override + public Tuple getAggregateTuple(final Tuple group) { + final Accumulator accumulator = getMainAccumulator(group); + final AggregateResult result = operator.getAggregate(accumulator); + return tupleFromAggregateResult(group, result); + } + + @Override + public AggregateResult getAggregateResult(final Tuple group) { + final Accumulator accumulator = getMainAccumulator(group); + return operator.getAggregate(accumulator); + } + + @Override + public Map> getAggregateResultTimeline(Tuple key) { + throw new UnsupportedOperationException(); + } + + @Override + public Map> getAggregateTupleTimeline(Tuple key) { + throw new UnsupportedOperationException(); + } + + /** + * @since 1.6 + */ + protected Accumulator getMainAccumulator(final Tuple key) { + return getAccumulator(key, memory); + } + + /** + * @since 1.6 + */ + protected Accumulator getRederivableAccumulator(final Tuple key) { + return getAccumulator(key, rederivableMemory); + } + + /** + * @since 1.6 + */ + protected Accumulator getAccumulator(final Tuple key, final Map memory) { + Accumulator accumulator = memory.get(key); + if (accumulator == null) { + return operator.createNeutral(); + } else { + return accumulator; + } + } + + @Override + public CommunicationGroup getCurrentGroup() { + return currentGroup; + } + + @Override + public void setCurrentGroup(final CommunicationGroup currentGroup) { + this.currentGroup = currentGroup; + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/CountNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/CountNode.java new file mode 100644 index 00000000..7c98de0d --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/CountNode.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2004-2009 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.aggregation; + +import java.util.Collection; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; + +/** + * An aggregation node that simply counts the number of tuples conforming to the signature. + * + * @author Gabor Bergmann + * @since 1.4 + */ +public class CountNode extends IndexerBasedAggregatorNode { + + public CountNode(ReteContainer reteContainer) { + super(reteContainer); + } + + int sizeOf(Collection group) { + return group == null ? 0 : group.size(); + } + + @Override + public Object aggregateGroup(Tuple signature, Collection group) { + return sizeOf(group); + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/GroupedMap.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/GroupedMap.java new file mode 100644 index 00000000..3c7850de --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/GroupedMap.java @@ -0,0 +1,120 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.aggregation; + +import java.util.AbstractMap.SimpleEntry; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; + +/** + * An optimized {@link Map} implementation where each key is produced by joining together a group tuple and some other + * object (via left inheritance). Only a select few {@link Map} operations are supported. This collection is + * unmodifiable. + * + * Operations on this map assume that client queries also obey the contract that keys are constructed from a group tuple + * and an additional object. + * + * @author Tamas Szabo + * @since 2.4 + */ +public class GroupedMap implements Map { + + protected final Tuple group; + // cached group size value is to be used in get() + private final int groupSize; + protected final Map mappings; + protected final IQueryRuntimeContext runtimeContext; + + public GroupedMap(final Tuple group, final Map mappings, + final IQueryRuntimeContext runtimeContext) { + this.group = group; + this.groupSize = group.getSize(); + this.mappings = mappings; + this.runtimeContext = runtimeContext; + } + + @Override + public int size() { + return this.mappings.size(); + } + + @Override + public boolean isEmpty() { + return this.mappings.isEmpty(); + } + + @Override + public boolean containsKey(final Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean containsValue(final Object value) { + return this.mappings.containsValue(value); + } + + @Override + public ValueType get(final Object key) { + if (key instanceof Tuple) { + final Object value = ((Tuple) key).get(this.groupSize); + final Object unwrappedValue = this.runtimeContext.unwrapElement(value); + return this.mappings.get(unwrappedValue); + } else { + return null; + } + } + + @Override + public ValueType put(final Tuple key, final ValueType value) { + throw new UnsupportedOperationException(); + } + + @Override + public ValueType remove(final Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public void putAll(final Map map) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public Set keySet() { + return new GroupedSet(this.group, this.mappings.keySet(), (g, v) -> { + return Tuples.staticArityLeftInheritanceTupleOf(g, this.runtimeContext.wrapElement(v)); + }); + } + + @Override + public Collection values() { + return this.mappings.values(); + } + + @Override + public Set> entrySet() { + return new GroupedSet>(this.group, this.mappings.keySet(), + (g, v) -> { + final Tuple key = Tuples.staticArityLeftInheritanceTupleOf(g, this.runtimeContext.wrapElement(v)); + final ValueType value = this.mappings.get(v); + return new SimpleEntry(key, value); + }); + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/GroupedSet.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/GroupedSet.java new file mode 100644 index 00000000..65561e53 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/GroupedSet.java @@ -0,0 +1,114 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.aggregation; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; +import java.util.function.BiFunction; + +/** + * An optimized {@link Set} implementation where each contained value is produced by combining together a grouping value + * and some other (key) object. The way of combining together these two values is specified by the closure passed to the + * constructor. Only a select few {@link Set} operations are supported. This collection is unmodifiable. + * + * @author Tamas Szabo + * @since 2.4 + */ +public class GroupedSet implements Set { + + protected final GroupingValueType group; + protected final Collection values; + protected final BiFunction valueFunc; + + public GroupedSet(final GroupingValueType group, final Collection values, + final BiFunction valueFunc) { + this.group = group; + this.values = values; + this.valueFunc = valueFunc; + } + + @Override + public int size() { + return this.values.size(); + } + + @Override + public boolean isEmpty() { + return this.values.isEmpty(); + } + + @Override + public boolean contains(final Object obj) { + throw new UnsupportedOperationException(); + } + + @Override + public Iterator iterator() { + final Iterator wrapped = this.values.iterator(); + return new Iterator() { + @Override + public boolean hasNext() { + return wrapped.hasNext(); + } + + @Override + public WholeKeyType next() { + final GroupedKeyType value = wrapped.next(); + return valueFunc.apply(group, value); + } + }; + } + + @Override + public Object[] toArray() { + throw new UnsupportedOperationException(); + } + + @Override + public T[] toArray(final T[] arr) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean add(final WholeKeyType tuple) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(final Object obj) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean containsAll(final Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(final Collection coll) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/IAggregatorNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/IAggregatorNode.java new file mode 100644 index 00000000..6c286364 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/IAggregatorNode.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.aggregation; + +import tools.refinery.viatra.runtime.rete.index.Indexer; + +/** + * Expresses that aggregators expose specialized non-enumerable indexers for outer joining. + * @author Gabor Bergmann + * + * @since 1.4 + * + */ +public interface IAggregatorNode { + + Indexer getAggregatorOuterIndexer(); + + Indexer getAggregatorOuterIdentityIndexer(int resultPositionInSignature); + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/IndexerBasedAggregatorNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/IndexerBasedAggregatorNode.java new file mode 100644 index 00000000..d9a94a82 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/IndexerBasedAggregatorNode.java @@ -0,0 +1,278 @@ +/******************************************************************************* + * Copyright (c) 2004-2009 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.aggregation; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.index.DefaultIndexerListener; +import tools.refinery.viatra.runtime.rete.index.Indexer; +import tools.refinery.viatra.runtime.rete.index.ProjectionIndexer; +import tools.refinery.viatra.runtime.rete.index.StandardIndexer; +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.StandardNode; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.traceability.TraceInfo; + +/** + * A special node depending on a projection indexer to aggregate tuple groups with the same projection. Only propagates + * the aggregates of non-empty groups. Use the outer indexers to circumvent. + *

    + * This node cannot be used in recursive differential dataflow evaluation. + * + * @author Gabor Bergmann + * @since 1.4 + */ +public abstract class IndexerBasedAggregatorNode extends StandardNode implements IAggregatorNode { + + ProjectionIndexer projection; + IndexerBasedAggregatorNode me; + int sourceWidth; + Map mainAggregates; + + AggregatorOuterIndexer aggregatorOuterIndexer = null; + AggregatorOuterIdentityIndexer[] aggregatorOuterIdentityIndexers = null; + + /** + * MUST call initializeWith() afterwards! + */ + public IndexerBasedAggregatorNode(ReteContainer reteContainer) { + super(reteContainer); + this.me = this; + mainAggregates = CollectionsFactory.createMap(); + } + + @Override + public void networkStructureChanged() { + if (this.reteContainer.isTimelyEvaluation() && this.reteContainer.getCommunicationTracker().isInRecursiveGroup(this)) { + throw new IllegalStateException(this.toString() + " cannot be used in recursive differential dataflow evaluation!"); + } + super.networkStructureChanged(); + } + + /** + * @param projection + * the projection indexer whose tuple groups should be aggregated + */ + public void initializeWith(ProjectionIndexer projection) { + this.projection = projection; + this.sourceWidth = projection.getMask().indices.length; + + for (Tuple signature : projection.getSignatures()) { + mainAggregates.put(signature, aggregateGroup(signature, projection.get(signature))); + } + projection.attachListener(new DefaultIndexerListener(this) { + @Override + public void notifyIndexerUpdate(Direction direction, Tuple updateElement, Tuple signature, boolean change, Timestamp timestamp) { + aggregateUpdate(direction, updateElement, signature, change); + } + }); + } + + /** + * Aggregates (reduces) a group of tuples. The group can be null. + */ + public abstract Object aggregateGroup(Tuple signature, Collection group); + + + /** + * Aggregates (reduces) a group of tuples, having access to the previous aggregated value (before the update) and + * the update definition. Defaults to aggregateGroup(). Override to increase performance. + * @since 2.4 + */ + public Object aggregateGroupAfterUpdate(Tuple signature, Collection currentGroup, Object oldAggregate, + Direction direction, Tuple updateElement, boolean change) { + return aggregateGroup(signature, currentGroup); + } + + protected Tuple aggregateAndPack(Tuple signature, Collection group) { + return packResult(signature, aggregateGroup(signature, group)); + } + + @Override + public Indexer getAggregatorOuterIndexer() { + if (aggregatorOuterIndexer == null) { + aggregatorOuterIndexer = new AggregatorOuterIndexer(); + this.getCommunicationTracker().registerDependency(this, aggregatorOuterIndexer); + // reteContainer.connectAndSynchronize(this, aggregatorOuterIndexer); + } + return aggregatorOuterIndexer; + } + + @Override + public Indexer getAggregatorOuterIdentityIndexer(int resultPositionInSignature) { + if (aggregatorOuterIdentityIndexers == null) + aggregatorOuterIdentityIndexers = new AggregatorOuterIdentityIndexer[sourceWidth + 1]; + if (aggregatorOuterIdentityIndexers[resultPositionInSignature] == null) { + aggregatorOuterIdentityIndexers[resultPositionInSignature] = new AggregatorOuterIdentityIndexer( + resultPositionInSignature); + this.getCommunicationTracker().registerDependency(this, aggregatorOuterIdentityIndexers[resultPositionInSignature]); + // reteContainer.connectAndSynchronize(this, aggregatorOuterIdentityIndexers[resultPositionInSignature]); + } + return aggregatorOuterIdentityIndexers[resultPositionInSignature]; + } + + @Override + public void pullInto(final Collection collector, final boolean flush) { + for (final Entry aggregateEntry : mainAggregates.entrySet()) { + collector.add(packResult(aggregateEntry.getKey(), aggregateEntry.getValue())); + } + } + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + // use all zero timestamps because this node cannot be used in recursive groups anyway + for (final Entry aggregateEntry : mainAggregates.entrySet()) { + collector.put(packResult(aggregateEntry.getKey(), aggregateEntry.getValue()), Timestamp.INSERT_AT_ZERO_TIMELINE); + } + } + + protected Tuple packResult(Tuple signature, Object result) { + return Tuples.staticArityLeftInheritanceTupleOf(signature, result); + } + + /** + * @since 2.4 + */ + protected void aggregateUpdate(Direction direction, Tuple updateElement, Tuple signature, boolean change) { + Collection currentGroup = projection.get(signature); + // these will be null if group is empty + Object oldAggregate = mainAggregates.get(signature); + Object safeOldAggregate = oldAggregate == null ? aggregateGroup(signature, null) : oldAggregate; + boolean empty = currentGroup == null || currentGroup.isEmpty(); + Object newAggregate = empty ? null : aggregateGroupAfterUpdate(signature, currentGroup, safeOldAggregate/* + * non-null + */, + direction, updateElement, change); + if (!empty) + mainAggregates.put(signature, newAggregate); + else + mainAggregates.remove(signature); + Tuple oldTuple = packResult(signature, safeOldAggregate); + Tuple newTuple = packResult(signature, newAggregate == null ? aggregateGroup(signature, null) : newAggregate); + if (oldAggregate != null) + propagateUpdate(Direction.DELETE, oldTuple, Timestamp.ZERO); // direct outputs lack non-empty groups + if (newAggregate != null) + propagateUpdate(Direction.INSERT, newTuple, Timestamp.ZERO); // direct outputs lack non-empty groups + if (aggregatorOuterIndexer != null) + aggregatorOuterIndexer.propagate(signature, oldTuple, newTuple); + if (aggregatorOuterIdentityIndexers != null) + for (AggregatorOuterIdentityIndexer aggregatorOuterIdentityIndexer : aggregatorOuterIdentityIndexers) + if (aggregatorOuterIdentityIndexer != null) + aggregatorOuterIdentityIndexer.propagate(signature, oldTuple, newTuple); + } + + private Object getAggregate(Tuple signature) { + Object aggregate = mainAggregates.get(signature); + return aggregate == null ? aggregateGroup(signature, null) : aggregate; + } + + @Override + public void assignTraceInfo(TraceInfo traceInfo) { + super.assignTraceInfo(traceInfo); + if (traceInfo.propagateToIndexerParent() && projection != null) + projection.acceptPropagatedTraceInfo(traceInfo); + } + + /** + * A special non-iterable index that retrieves the aggregated, packed result (signature+aggregate) for the original + * signature. + * + * @author Gabor Bergmann + */ + class AggregatorOuterIndexer extends StandardIndexer { + + public AggregatorOuterIndexer() { + super(me.reteContainer, TupleMask.omit(sourceWidth, sourceWidth + 1)); + this.parent = me; + } + + @Override + public Collection get(Tuple signature) { + return Collections.singleton(packResult(signature, getAggregate(signature))); + } + + public void propagate(Tuple signature, Tuple oldTuple, Tuple newTuple) { + propagate(Direction.INSERT, newTuple, signature, false, Timestamp.ZERO); + propagate(Direction.DELETE, oldTuple, signature, false, Timestamp.ZERO); + } + + @Override + public Node getActiveNode() { + return projection.getActiveNode(); + } + + } + + /** + * A special non-iterable index that checks a suspected aggregate value for a given signature. The signature for + * this index is the original signature of the projection index, with the suspected result inserted at position + * resultPositionInSignature. + * + * @author Gabor Bergmann + */ + + class AggregatorOuterIdentityIndexer extends StandardIndexer { + int resultPositionInSignature; + TupleMask pruneResult; + TupleMask reorderMask; + + public AggregatorOuterIdentityIndexer(int resultPositionInSignature) { + super(me.reteContainer, TupleMask.displace(sourceWidth, resultPositionInSignature, sourceWidth + 1)); + this.parent = me; + // this.localAggregates = new HashMap(); + this.resultPositionInSignature = resultPositionInSignature; + this.pruneResult = TupleMask.omit(resultPositionInSignature, sourceWidth + 1); + if (resultPositionInSignature == sourceWidth) + this.reorderMask = null; + else + this.reorderMask = mask; + } + + @Override + public Collection get(Tuple signatureWithResult) { + Tuple prunedSignature = pruneResult.transform(signatureWithResult); + Object result = getAggregate(prunedSignature); + if (signatureWithResult.get(resultPositionInSignature).equals(result)) + return Collections.singleton(signatureWithResult); + else + return null; + } + + public void propagate(Tuple signature, Tuple oldTuple, Tuple newTuple) { + propagate(Direction.INSERT, reorder(newTuple), signature, true, Timestamp.ZERO); + propagate(Direction.DELETE, reorder(oldTuple), signature, true, Timestamp.ZERO); + } + + private Tuple reorder(Tuple signatureWithResult) { + Tuple transformed; + if (reorderMask == null) + transformed = signatureWithResult; + else + transformed = reorderMask.transform(signatureWithResult); + return transformed; + } + + @Override + public Node getActiveNode() { + return projection.getActiveNode(); + } + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FaithfulParallelTimelyColumnAggregatorNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FaithfulParallelTimelyColumnAggregatorNode.java new file mode 100644 index 00000000..a9863400 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FaithfulParallelTimelyColumnAggregatorNode.java @@ -0,0 +1,217 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.aggregation.timely; + +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.TreeMap; + +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.IDeltaBag; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; +import tools.refinery.viatra.runtime.matchers.util.Signed; +import tools.refinery.viatra.runtime.matchers.util.timeline.Diff; +import tools.refinery.viatra.runtime.rete.aggregation.timely.FaithfulParallelTimelyColumnAggregatorNode.CumulativeAggregate; +import tools.refinery.viatra.runtime.rete.aggregation.timely.FaithfulParallelTimelyColumnAggregatorNode.FoldingState; +import tools.refinery.viatra.runtime.rete.aggregation.timely.FaithfulTimelyColumnAggregatorNode.MergeableFoldingState; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.communication.timely.ResumableNode; + +/** + * Faithful column aggregator with parallel aggregation architecture. + * + * @author Tamas Szabo + * @since 2.4 + * + */ +public class FaithfulParallelTimelyColumnAggregatorNode extends + FaithfulTimelyColumnAggregatorNode, FoldingState> + implements ResumableNode { + + public FaithfulParallelTimelyColumnAggregatorNode(final ReteContainer reteContainer, + final IMultisetAggregationOperator operator, + final TupleMask groupMask, final TupleMask columnMask) { + super(reteContainer, operator, groupMask, columnMask); + } + + public FaithfulParallelTimelyColumnAggregatorNode(final ReteContainer reteContainer, + final IMultisetAggregationOperator operator, + final TupleMask groupMask, final int aggregatedColumn) { + this(reteContainer, operator, groupMask, TupleMask.selectSingle(aggregatedColumn, groupMask.sourceWidth)); + } + + @Override + protected Map> doFoldingStep(final Tuple group, final FoldingState state, + final Timestamp timestamp) { + final CumulativeAggregate aggregate = getAggregate(group, timestamp); + if (state.delta.isEmpty()) { + gcAggregates(aggregate, group, timestamp); + return Collections.emptyMap(); + } else { + final Map> diffMap = CollectionsFactory.createMap(); + final Timestamp nextTimestamp = this.aggregates.get(group).higherKey(timestamp); + + final AggregateResult currentOldResult = operator.getAggregate(aggregate.accumulator); + + for (final Entry entry : state.delta.entriesWithMultiplicities()) { + final boolean isInsertion = entry.getValue() > 0; + final Domain aggregand = entry.getKey(); + for (int i = 0; i < Math.abs(entry.getValue()); i++) { + aggregate.accumulator = operator.update(aggregate.accumulator, aggregand, isInsertion); + } + } + + final AggregateResult currentNewResult = operator.getAggregate(aggregate.accumulator); + + if (!Objects.equals(currentOldResult, currentNewResult)) { + // current old result disappears here + appendDiff(currentOldResult, new Signed<>(Direction.DELETE, timestamp), diffMap); + if (nextTimestamp != null) { + appendDiff(currentOldResult, new Signed<>(Direction.INSERT, nextTimestamp), diffMap); + } + + // current new result appears here + appendDiff(currentNewResult, new Signed<>(Direction.INSERT, timestamp), diffMap); + if (nextTimestamp != null) { + appendDiff(currentNewResult, new Signed<>(Direction.DELETE, nextTimestamp), diffMap); + } + } + + gcAggregates(aggregate, group, timestamp); + updateTimeline(group, diffMap); + + // prepare folding state for next timestamp + if (nextTimestamp != null) { + final FoldingState newState = new FoldingState<>(); + newState.delta = state.delta; + addFoldingState(group, newState, nextTimestamp); + } + + return diffMap; + } + } + + @Override + public void update(final Direction direction, final Tuple update, final Timestamp timestamp) { + final Tuple group = groupMask.transform(update); + final Tuple value = columnMask.transform(update); + @SuppressWarnings("unchecked") + final Domain aggregand = (Domain) runtimeContext.unwrapElement(value.get(0)); + final boolean isInsertion = direction == Direction.INSERT; + + final CumulativeAggregate aggregate = getAggregate(group, timestamp); + final FoldingState state = new FoldingState<>(); + if (isInsertion) { + aggregate.aggregands.addOne(aggregand); + state.delta.addOne(aggregand); + } else { + aggregate.aggregands.removeOne(aggregand); + state.delta.removeOne(aggregand); + } + + addFoldingState(group, state, timestamp); + } + + /** + * Garbage collects the counter of the given group and timestamp if the bag of aggregands is empty. + */ + @Override + protected void gcAggregates(final CumulativeAggregate aggregate, final Tuple group, + final Timestamp timestamp) { + if (aggregate.aggregands.isEmpty()) { + final TreeMap> groupAggregates = this.aggregates + .get(group); + groupAggregates.remove(timestamp); + if (groupAggregates.isEmpty()) { + this.aggregates.remove(group); + } + } + } + + /** + * On-demand initializes and returns the aggregate for the given group and timestamp. + */ + @Override + protected CumulativeAggregate getAggregate(final Tuple group, final Timestamp timestamp) { + final TreeMap> groupAggregates = this.aggregates + .computeIfAbsent(group, k -> CollectionsFactory.createTreeMap()); + return groupAggregates.computeIfAbsent(timestamp, k -> { + final CumulativeAggregate aggregate = new CumulativeAggregate<>(); + final Entry> lowerEntry = groupAggregates + .lowerEntry(timestamp); + if (lowerEntry == null) { + aggregate.accumulator = operator.createNeutral(); + } else { + aggregate.accumulator = operator.clone(lowerEntry.getValue().accumulator); + } + return aggregate; + }); + } + + @Override + public AggregateResult getAggregateResult(final Tuple group) { + final TreeMap> groupAggregates = this.aggregates.get(group); + if (groupAggregates != null) { + final Entry> lastEntry = groupAggregates.lastEntry(); + return operator.getAggregate(lastEntry.getValue().accumulator); + } else { + return NEUTRAL; + } + } + + protected static class CumulativeAggregate { + protected Accumulator accumulator; + protected IDeltaBag aggregands; + + protected CumulativeAggregate() { + this.aggregands = CollectionsFactory.createDeltaBag(); + } + + @Override + public String toString() { + return "accumulator=" + accumulator + " aggregands=" + aggregands; + } + } + + protected static class FoldingState implements MergeableFoldingState> { + protected IDeltaBag delta; + + protected FoldingState() { + this.delta = CollectionsFactory.createDeltaBag(); + } + + @Override + public String toString() { + return "delta=" + delta; + } + + /** + * The returned result will never be null, even if the resulting delta set is empty. + */ + @Override + public FoldingState merge(final FoldingState that) { + Preconditions.checkArgument(that != null); + // 'this' was the previously registered folding state + // 'that' is the new folding state being pushed upwards + final FoldingState result = new FoldingState<>(); + this.delta.forEachEntryWithMultiplicities((d, m) -> result.delta.addSigned(d, m)); + that.delta.forEachEntryWithMultiplicities((d, m) -> result.delta.addSigned(d, m)); + return result; + } + + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FaithfulSequentialTimelyColumnAggregatorNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FaithfulSequentialTimelyColumnAggregatorNode.java new file mode 100644 index 00000000..666b2051 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FaithfulSequentialTimelyColumnAggregatorNode.java @@ -0,0 +1,280 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.aggregation.timely; + +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.TreeMap; + +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.IDeltaBag; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; +import tools.refinery.viatra.runtime.matchers.util.Signed; +import tools.refinery.viatra.runtime.matchers.util.timeline.Diff; +import tools.refinery.viatra.runtime.rete.aggregation.timely.FaithfulSequentialTimelyColumnAggregatorNode.CumulativeAggregate; +import tools.refinery.viatra.runtime.rete.aggregation.timely.FaithfulSequentialTimelyColumnAggregatorNode.FoldingState; +import tools.refinery.viatra.runtime.rete.aggregation.timely.FaithfulTimelyColumnAggregatorNode.MergeableFoldingState; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.communication.timely.ResumableNode; + +/** + * Faithful column aggregator with sequential aggregation architecture. + * + * @author Tamas Szabo + * @since 2.4 + * + */ +public class FaithfulSequentialTimelyColumnAggregatorNode extends + FaithfulTimelyColumnAggregatorNode, FoldingState> + implements ResumableNode { + + protected boolean isRecursiveAggregation; + + public FaithfulSequentialTimelyColumnAggregatorNode(final ReteContainer reteContainer, + final IMultisetAggregationOperator operator, + final TupleMask groupMask, final TupleMask columnMask) { + super(reteContainer, operator, groupMask, columnMask); + this.isRecursiveAggregation = false; + } + + @Override + public void networkStructureChanged() { + super.networkStructureChanged(); + this.isRecursiveAggregation = this.reteContainer.getCommunicationTracker().isInRecursiveGroup(this); + } + + @Override + protected Map> doFoldingStep(final Tuple group, + final FoldingState state, final Timestamp timestamp) { + final CumulativeAggregate aggregate = getAggregate(group, timestamp); + if (state.delta.isEmpty() && Objects.equals(state.oldResult, state.newResult)) { + gcAggregates(aggregate, group, timestamp); + return Collections.emptyMap(); + } else { + final Map> diffMap = CollectionsFactory.createMap(); + final Timestamp nextTimestamp = this.aggregates.get(group).higherKey(timestamp); + + final AggregateResult previousOldResult = state.oldResult; + final AggregateResult previousNewResult = state.newResult; + + final AggregateResult currentOldResult = previousOldResult == null + ? operator.getAggregate(aggregate.positive) + : operator.combine(previousOldResult, aggregate.positive); + + for (final Entry entry : state.delta.entriesWithMultiplicities()) { + final boolean isInsertion = entry.getValue() > 0; + final Domain aggregand = entry.getKey(); + if (isInsertion) { + for (int i = 0; i < entry.getValue(); i++) { + if (isRecursiveAggregation) { + final boolean contains = aggregate.negative.containsNonZero(aggregand); + if (contains) { + aggregate.negative.addOne(aggregand); + } else { + aggregate.positive = operator.update(aggregate.positive, aggregand, true); + } + } else { + aggregate.positive = operator.update(aggregate.positive, aggregand, true); + } + } + } else { + for (int i = 0; i < -entry.getValue(); i++) { + if (isRecursiveAggregation) { + final boolean contains = operator.contains(aggregand, aggregate.positive); + if (contains) { + aggregate.positive = operator.update(aggregate.positive, aggregand, false); + } else { + aggregate.negative.removeOne(aggregand); + } + } else { + aggregate.positive = operator.update(aggregate.positive, aggregand, false); + } + } + } + } + + final AggregateResult currentNewResult = previousNewResult == null + ? operator.getAggregate(aggregate.positive) + : operator.combine(previousNewResult, aggregate.positive); + + aggregate.cachedResult = currentNewResult; + + final boolean sameResult = Objects.equals(currentOldResult, currentNewResult); + if (!sameResult) { + // current old result disappears here + appendDiff(currentOldResult, new Signed<>(Direction.DELETE, timestamp), diffMap); + if (nextTimestamp != null) { + appendDiff(currentOldResult, new Signed<>(Direction.INSERT, nextTimestamp), diffMap); + } + + // current new result appears here + appendDiff(currentNewResult, new Signed<>(Direction.INSERT, timestamp), diffMap); + if (nextTimestamp != null) { + appendDiff(currentNewResult, new Signed<>(Direction.DELETE, nextTimestamp), diffMap); + } + } + + gcAggregates(aggregate, group, timestamp); + updateTimeline(group, diffMap); + + // prepare folding state for next timestamp + if (nextTimestamp != null && !sameResult) { + final FoldingState newState = new FoldingState<>(); + // DO NOT push forward the delta in the folding state!!! that one only affects the input timestamp + newState.oldResult = currentOldResult; + newState.newResult = currentNewResult; + addFoldingState(group, newState, nextTimestamp); + } + + return diffMap; + } + } + + @Override + public void update(final Direction direction, final Tuple update, final Timestamp timestamp) { + final Tuple group = groupMask.transform(update); + final Tuple value = columnMask.transform(update); + @SuppressWarnings("unchecked") + final Domain aggregand = (Domain) runtimeContext.unwrapElement(value.get(0)); + final boolean isInsertion = direction == Direction.INSERT; + + final AggregateResult previousResult = getResultRaw(group, timestamp, true); + final FoldingState state = new FoldingState(); + if (isInsertion) { + state.delta.addOne(aggregand); + } else { + state.delta.removeOne(aggregand); + } + state.oldResult = previousResult; + state.newResult = previousResult; + + // it is acceptable if both oldResult and newResult are null at this point + // in that case we did not have a previous entry at a lower timestamp + + addFoldingState(group, state, timestamp); + } + + protected AggregateResult getResultRaw(final Tuple group, final Timestamp timestamp, final boolean lower) { + final TreeMap> entryMap = this.aggregates + .get(group); + if (entryMap == null) { + return null; + } else { + CumulativeAggregate aggregate = null; + if (lower) { + final Entry> lowerEntry = entryMap + .lowerEntry(timestamp); + if (lowerEntry != null) { + aggregate = lowerEntry.getValue(); + } + } else { + aggregate = entryMap.get(timestamp); + } + if (aggregate == null) { + return null; + } else { + return aggregate.cachedResult; + } + } + } + + @Override + protected void gcAggregates(final CumulativeAggregate aggregate, + final Tuple group, final Timestamp timestamp) { + if (operator.isNeutral(aggregate.positive) && aggregate.negative.isEmpty()) { + final TreeMap> groupAggregates = this.aggregates + .get(group); + groupAggregates.remove(timestamp); + if (groupAggregates.isEmpty()) { + this.aggregates.remove(group); + } + } + } + + @Override + protected CumulativeAggregate getAggregate(final Tuple group, + final Timestamp timestamp) { + final TreeMap> groupAggregates = this.aggregates + .computeIfAbsent(group, k -> CollectionsFactory.createTreeMap()); + return groupAggregates.computeIfAbsent(timestamp, k -> { + final CumulativeAggregate aggregate = new CumulativeAggregate<>(); + aggregate.positive = operator.createNeutral(); + return aggregate; + }); + } + + @Override + public AggregateResult getAggregateResult(final Tuple group) { + final TreeMap> groupAggregates = this.aggregates + .get(group); + if (groupAggregates != null) { + final Entry> lastEntry = groupAggregates + .lastEntry(); + return lastEntry.getValue().cachedResult; + } else { + return NEUTRAL; + } + } + + protected static class CumulativeAggregate { + protected Accumulator positive; + protected IDeltaBag negative; + protected AggregateResult cachedResult; + + protected CumulativeAggregate() { + this.negative = CollectionsFactory.createDeltaBag(); + } + + @Override + public String toString() { + return "positive=" + positive + " negative=" + negative + " cachedResult=" + cachedResult; + } + } + + protected static class FoldingState + implements MergeableFoldingState> { + protected IDeltaBag delta; + protected AggregateResult oldResult; + protected AggregateResult newResult; + + protected FoldingState() { + this.delta = CollectionsFactory.createDeltaBag(); + } + + @Override + public String toString() { + return "delta=" + delta + " oldResult=" + oldResult + " newResult=" + newResult; + } + + /** + * The returned result will never be null, even if the resulting delta set is empty. + */ + @Override + public FoldingState merge(final FoldingState that) { + Preconditions.checkArgument(that != null); + // 'this' was the previously registered folding state + // 'that' is the new folding state being pushed upwards + final FoldingState result = new FoldingState(); + this.delta.forEachEntryWithMultiplicities((d, m) -> result.delta.addSigned(d, m)); + that.delta.forEachEntryWithMultiplicities((d, m) -> result.delta.addSigned(d, m)); + result.oldResult = this.oldResult; + result.newResult = that.newResult; + return result; + } + + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FaithfulTimelyColumnAggregatorNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FaithfulTimelyColumnAggregatorNode.java new file mode 100644 index 00000000..8fe9a4e9 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FaithfulTimelyColumnAggregatorNode.java @@ -0,0 +1,247 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.aggregation.timely; + +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.TreeMap; + +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Signed; +import tools.refinery.viatra.runtime.matchers.util.timeline.Diff; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timelines; +import tools.refinery.viatra.runtime.rete.aggregation.AbstractColumnAggregatorNode; +import tools.refinery.viatra.runtime.rete.aggregation.GroupedMap; +import tools.refinery.viatra.runtime.rete.aggregation.timely.FaithfulTimelyColumnAggregatorNode.MergeableFoldingState; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.communication.timely.ResumableNode; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.timely.TimelyMailbox; + +/** + * Faithful timely implementation of the column aggregator node. Complete timelines (series of appearance & + * disappearance) are maintained for tuples.
    + *
    + * Subclasses are responsible for implementing the aggregator architecture, and they must use the CumulativeAggregate + * type parameter for that.
    + *
    + * This node supports recursive aggregation. + * + * @author Tamas Szabo + * @since 2.4 + */ +public abstract class FaithfulTimelyColumnAggregatorNode> + extends AbstractColumnAggregatorNode implements ResumableNode { + + protected final Map> aggregates; + protected final Map>> timelines; + protected final TreeMap> foldingState; + protected CommunicationGroup communicationGroup; + + public FaithfulTimelyColumnAggregatorNode(final ReteContainer reteContainer, + final IMultisetAggregationOperator operator, + final TupleMask groupMask, final TupleMask columnMask) { + super(reteContainer, operator, groupMask, columnMask); + this.aggregates = CollectionsFactory.createMap(); + this.timelines = CollectionsFactory.createMap(); + this.foldingState = CollectionsFactory.createTreeMap(); + // mailbox MUST be instantiated after the fields are all set + this.mailbox = instantiateMailbox(); + } + + @Override + protected Mailbox instantiateMailbox() { + return new TimelyMailbox(this, this.reteContainer); + } + + @Override + public void clear() { + this.mailbox.clear(); + this.aggregates.clear(); + this.timelines.clear(); + this.children.clear(); + this.childMailboxes.clear(); + this.foldingState.clear(); + } + + /** + * Registers the given folding state for the specified timestamp and tuple. If there is already a state stored, the + * two states will be merged together. + * + * + */ + protected void addFoldingState(final Tuple group, final FoldingState state, final Timestamp timestamp) { + // assert !state.delta.isEmpty(); + final Map tupleMap = this.foldingState.computeIfAbsent(timestamp, + k -> CollectionsFactory.createMap()); + tupleMap.compute(group, (k, v) -> { + return v == null ? state : v.merge(state); + }); + } + + @Override + public Timestamp getResumableTimestamp() { + if (this.foldingState.isEmpty()) { + return null; + } else { + return this.foldingState.firstKey(); + } + } + + @Override + public void resumeAt(final Timestamp timestamp) { + Timestamp current = this.getResumableTimestamp(); + if (current == null) { + throw new IllegalStateException("There is nothing to fold!"); + } else if (current.compareTo(timestamp) != 0) { + throw new IllegalStateException("Expected to continue folding at " + timestamp + "!"); + } + + final Map tupleMap = this.foldingState.remove(timestamp); + for (final Entry groupEntry : tupleMap.entrySet()) { + final Tuple group = groupEntry.getKey(); + final FoldingState value = groupEntry.getValue(); + final Map> diffMap = doFoldingStep(group, value, timestamp); + for (final Entry> resultEntry : diffMap.entrySet()) { + for (final Signed signed : resultEntry.getValue()) { + propagate(signed.getDirection(), group, resultEntry.getKey(), signed.getPayload()); + } + } + } + + final Timestamp nextTimestamp = this.getResumableTimestamp(); + if (Objects.equals(timestamp, nextTimestamp)) { + throw new IllegalStateException( + "Folding at " + timestamp + " produced more folding work at the same timestamp!"); + } else if (nextTimestamp != null) { + this.communicationGroup.notifyHasMessage(this.mailbox, nextTimestamp); + } + } + + protected abstract Map> doFoldingStep(final Tuple group, final FoldingState state, + final Timestamp timestamp); + + /** + * Updates and garbage collects the timeline of the given tuple based on the given diffs. + */ + protected void updateTimeline(final Tuple group, final Map> diffs) { + if (!diffs.isEmpty()) { + this.timelines.compute(group, (k, resultTimelines) -> { + if (resultTimelines == null) { + resultTimelines = CollectionsFactory.createMap(); + } + for (final Entry> entry : diffs.entrySet()) { + final AggregateResult result = entry.getKey(); + resultTimelines.compute(result, (k2, oldResultTimeline) -> { + final Diff currentResultDiffs = entry.getValue(); + if (oldResultTimeline == null) { + oldResultTimeline = getInitialTimeline(result); + } + final Timeline timeline = oldResultTimeline.mergeAdditive(currentResultDiffs); + if (timeline.isEmpty()) { + return null; + } else { + return timeline; + } + }); + } + if (resultTimelines.isEmpty()) { + return null; + } else { + return resultTimelines; + } + }); + } + } + + /** + * Garbage collects the counter of the given group and timestamp if the bag of aggregands is empty. + */ + protected abstract void gcAggregates(final CumulativeAggregate aggregate, final Tuple group, + final Timestamp timestamp); + + /** + * On-demand initializes and returns the aggregate for the given group and timestamp. + */ + protected abstract CumulativeAggregate getAggregate(final Tuple group, final Timestamp timestamp); + + protected static final Timeline NEUTRAL_INITIAL_TIMELINE = Timestamp.INSERT_AT_ZERO_TIMELINE; + protected static final Timeline NON_NEUTRAL_INITIAL_TIMELINE = Timelines.createEmpty(); + + protected Timeline getInitialTimeline(final AggregateResult result) { + if (NEUTRAL == result) { + return NEUTRAL_INITIAL_TIMELINE; + } else { + return NON_NEUTRAL_INITIAL_TIMELINE; + } + } + + protected static void appendDiff(final AggregateResult result, final Signed diff, + final Map> diffs) { + if (result != null) { + diffs.compute(result, (k, timeLineDiff) -> { + if (timeLineDiff == null) { + timeLineDiff = new Diff<>(); + } + timeLineDiff.add(diff); + return timeLineDiff; + }); + } + } + + @Override + public Tuple getAggregateTuple(final Tuple group) { + return tupleFromAggregateResult(group, getAggregateResult(group)); + } + + @Override + public Map> getAggregateResultTimeline(final Tuple group) { + final Map> resultTimelines = this.timelines.get(group); + if (resultTimelines == null) { + if (NEUTRAL == null) { + return Collections.emptyMap(); + } else { + return Collections.singletonMap(NEUTRAL, NEUTRAL_INITIAL_TIMELINE); + } + } else { + return resultTimelines; + } + } + + @Override + public Map> getAggregateTupleTimeline(final Tuple group) { + final Map> resultTimelines = getAggregateResultTimeline(group); + return new GroupedMap>(group, resultTimelines, this.runtimeContext); + } + + @Override + public CommunicationGroup getCurrentGroup() { + return communicationGroup; + } + + @Override + public void setCurrentGroup(final CommunicationGroup currentGroup) { + this.communicationGroup = currentGroup; + } + + protected interface MergeableFoldingState { + + public abstract T merge(final T that); + + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FirstOnlyParallelTimelyColumnAggregatorNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FirstOnlyParallelTimelyColumnAggregatorNode.java new file mode 100644 index 00000000..733d2585 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FirstOnlyParallelTimelyColumnAggregatorNode.java @@ -0,0 +1,106 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.aggregation.timely; + +import java.util.Map.Entry; +import java.util.TreeMap; + +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * First-only column aggregator with parallel aggregation architecture. + * + * @author Tamas Szabo + * @since 2.4 + */ +public class FirstOnlyParallelTimelyColumnAggregatorNode + extends FirstOnlyTimelyColumnAggregatorNode { + + public FirstOnlyParallelTimelyColumnAggregatorNode(final ReteContainer reteContainer, + final IMultisetAggregationOperator operator, + final TupleMask groupMask, final TupleMask columnMask) { + super(reteContainer, operator, groupMask, columnMask); + } + + /** + * Accumulator gets modified at the input timestamp and at all higher timestamps. Folding cannot be interrupted if + * the new aggregate result is the same as the old at an intermediate timestamp because aggregands need to be copied + * over to all accumulators at the higher timestamps. + */ + @Override + public void update(final Direction direction, final Tuple update, final Timestamp timestamp) { + final Tuple group = groupMask.transform(update); + final Tuple value = columnMask.transform(update); + @SuppressWarnings("unchecked") + final Domain aggregand = (Domain) runtimeContext.unwrapElement(value.get(0)); + final boolean isInsertion = direction == Direction.INSERT; + + final AggregateResult previousResult = getResultRaw(group, timestamp, true); + + Accumulator oldAccumulator = getAccumulator(group, timestamp); + AggregateResult oldResult = operator.getAggregate(oldAccumulator); + + Accumulator newAccumulator = operator.update(oldAccumulator, aggregand, isInsertion); + AggregateResult newResult = operator.getAggregate(newAccumulator); + + storeIfNotNeutral(group, newAccumulator, newResult, timestamp); + + propagateWithChecks(group, timestamp, previousResult, previousResult, oldResult, newResult); + + AggregateResult previousOldResult = oldResult; + AggregateResult previousNewResult = newResult; + final TreeMap> groupEntries = this.memory + .get(group); + + Timestamp currentTimestamp = groupEntries == null ? null : groupEntries.higherKey(timestamp); + + while (currentTimestamp != null) { + final CumulativeAggregate groupEntry = groupEntries.get(currentTimestamp); + oldResult = groupEntry.result; + oldAccumulator = groupEntry.accumulator; + newAccumulator = operator.update(oldAccumulator, aggregand, isInsertion); + newResult = operator.getAggregate(newAccumulator); + + storeIfNotNeutral(group, newAccumulator, newResult, currentTimestamp); + + propagateWithChecks(group, currentTimestamp, previousOldResult, previousNewResult, oldResult, newResult); + + previousOldResult = oldResult; + previousNewResult = newResult; + currentTimestamp = groupEntries.higherKey(currentTimestamp); + } + } + + @Override + protected Accumulator getAccumulator(final Tuple group, final Timestamp timestamp) { + final TreeMap> entryMap = this.memory.get(group); + if (entryMap == null) { + return operator.createNeutral(); + } else { + final CumulativeAggregate entry = entryMap.get(timestamp); + if (entry == null) { + final Entry> lowerEntry = entryMap + .lowerEntry(timestamp); + if (lowerEntry == null) { + return operator.createNeutral(); + } else { + return operator.clone(lowerEntry.getValue().accumulator); + } + } else { + return entry.accumulator; + } + } + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FirstOnlySequentialTimelyColumnAggregatorNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FirstOnlySequentialTimelyColumnAggregatorNode.java new file mode 100644 index 00000000..79197aac --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FirstOnlySequentialTimelyColumnAggregatorNode.java @@ -0,0 +1,117 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.aggregation.timely; + +import java.util.Objects; +import java.util.TreeMap; + +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * First-only column aggregator with sequential aggregation architecture. + * + * @author Tamas Szabo + * @since 2.4 + */ +public class FirstOnlySequentialTimelyColumnAggregatorNode + extends FirstOnlyTimelyColumnAggregatorNode { + + public FirstOnlySequentialTimelyColumnAggregatorNode(final ReteContainer reteContainer, + final IMultisetAggregationOperator operator, + final TupleMask groupMask, final TupleMask columnMask) { + super(reteContainer, operator, groupMask, columnMask); + } + + /** + * Accumulator gets modified only at the timestamp where the update happened. During the folding, accumulators are + * never changed at higher timestamps. Aggregate results at higher timestamps may change due to the change at the + * input timestamp. Uniqueness enforcement may require from aggregate results to jump up/down on demand during the + * folding. + */ + @Override + public void update(final Direction direction, final Tuple update, final Timestamp timestamp) { + final Tuple group = groupMask.transform(update); + final Tuple value = columnMask.transform(update); + @SuppressWarnings("unchecked") + final Domain aggregand = (Domain) runtimeContext.unwrapElement(value.get(0)); + final boolean isInsertion = direction == Direction.INSERT; + + final AggregateResult previousResult = getResultRaw(group, timestamp, true); + + final Accumulator oldAccumulator = getAccumulator(group, timestamp); + final AggregateResult oldResult = previousResult == null ? operator.getAggregate(oldAccumulator) + : operator.combine(previousResult, oldAccumulator); + + final Accumulator newAccumulator = operator.update(oldAccumulator, aggregand, isInsertion); + final AggregateResult newResult = previousResult == null ? operator.getAggregate(newAccumulator) + : operator.combine(previousResult, newAccumulator); + + storeIfNotNeutral(group, newAccumulator, newResult, timestamp); + + propagateWithChecks(group, timestamp, previousResult, previousResult, oldResult, newResult); + + // fold up the state towards higher timestamps + if (!Objects.equals(oldResult, newResult)) { + AggregateResult previousOldResult = oldResult; + AggregateResult previousNewResult = newResult; + AggregateResult currentOldResult = null; + AggregateResult currentNewResult = null; + final TreeMap> groupEntries = this.memory + .get(group); + + Timestamp currentTimestamp = groupEntries == null ? null : groupEntries.higherKey(timestamp); + + while (currentTimestamp != null) { + // they cannot be the same, otherwise we would not even be here + assert !Objects.equals(previousOldResult, previousNewResult); + + final Accumulator accumulator = getAccumulator(group, currentTimestamp); + currentOldResult = groupEntries.get(currentTimestamp).result; + currentNewResult = operator.combine(previousNewResult, accumulator); + + // otherwise we would not be iterating over this timestamp + assert !operator.isNeutral(accumulator); + + propagateWithChecks(group, currentTimestamp, previousOldResult, previousNewResult, currentOldResult, + currentNewResult); + + if (!Objects.equals(currentOldResult, currentNewResult)) { + storeIfNotNeutral(group, accumulator, currentNewResult, currentTimestamp); + previousOldResult = currentOldResult; + previousNewResult = currentNewResult; + currentTimestamp = groupEntries.higherKey(currentTimestamp); + } else { + // we can stop the folding from here + break; + } + } + } + } + + @Override + protected Accumulator getAccumulator(final Tuple group, final Timestamp timestamp) { + final TreeMap> entryMap = this.memory.get(group); + if (entryMap == null) { + return operator.createNeutral(); + } else { + final CumulativeAggregate entry = entryMap.get(timestamp); + if (entry == null) { + return operator.createNeutral(); + } else { + return entry.accumulator; + } + } + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FirstOnlyTimelyColumnAggregatorNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FirstOnlyTimelyColumnAggregatorNode.java new file mode 100644 index 00000000..0c73000e --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/aggregation/timely/FirstOnlyTimelyColumnAggregatorNode.java @@ -0,0 +1,212 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.aggregation.timely; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.TreeMap; + +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timelines; +import tools.refinery.viatra.runtime.rete.aggregation.AbstractColumnAggregatorNode; +import tools.refinery.viatra.runtime.rete.aggregation.GroupedMap; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.timely.TimelyMailbox; + +/** + * First-only timely implementation of the column aggregator node. Only timestamps of appearance are maintained for + * tuples instead of complete timelines. + *

    + * Subclasses are responsible for implementing the aggregator architecture, and they must make use of the inner class {@link CumulativeAggregate}. + *

    + * This node supports recursive aggregation. + * + * @author Tamas Szabo + * @since 2.4 + */ +public abstract class FirstOnlyTimelyColumnAggregatorNode + extends AbstractColumnAggregatorNode { + + protected final Map>> memory; + + public FirstOnlyTimelyColumnAggregatorNode(final ReteContainer reteContainer, + final IMultisetAggregationOperator operator, + final TupleMask groupMask, final TupleMask columnMask) { + super(reteContainer, operator, groupMask, columnMask); + this.memory = CollectionsFactory.createMap(); + // mailbox MUST be instantiated after the fields are all set + this.mailbox = instantiateMailbox(); + } + + protected static class CumulativeAggregate { + // the accumulator storing the aggregands + protected Accumulator accumulator; + // the aggregate result at the timestamp where this cumulative aggregate is stored + protected AggregateResult result; + + private CumulativeAggregate(final Accumulator accumulator, final AggregateResult result) { + this.accumulator = accumulator; + this.result = result; + } + + } + + public Collection getGroups() { + return this.memory.keySet(); + } + + public AggregateResult getLastResult(final Tuple group) { + final TreeMap> groupMap = this.memory.get(group); + if (groupMap == null) { + return null; + } else { + return groupMap.lastEntry().getValue().result; + } + } + + public Timestamp getLastTimestamp(final Tuple group) { + final TreeMap> groupMap = this.memory.get(group); + if (groupMap == null) { + return null; + } else { + return groupMap.lastEntry().getKey(); + } + } + + @Override + protected Mailbox instantiateMailbox() { + return new TimelyMailbox(this, this.reteContainer); + } + + @Override + public void clear() { + this.mailbox.clear(); + this.memory.clear(); + this.children.clear(); + this.childMailboxes.clear(); + } + + protected void propagateWithChecks(final Tuple group, final Timestamp timestamp, + final AggregateResult previousOldResult, final AggregateResult previousNewResult, + final AggregateResult currentOldResult, final AggregateResult currentNewResult) { + final boolean jumpDown = Objects.equals(previousNewResult, currentOldResult); + final boolean jumpUp = Objects.equals(previousOldResult, currentNewResult); + final boolean resultsDiffer = !Objects.equals(currentOldResult, currentNewResult); + + // uniqueness enforcement is happening here + if ((resultsDiffer || jumpDown) && !Objects.equals(previousOldResult, currentOldResult)) { + propagate(Direction.DELETE, group, currentOldResult, timestamp); + } + if ((resultsDiffer || jumpUp) && !Objects.equals(previousNewResult, currentNewResult)) { + propagate(Direction.INSERT, group, currentNewResult, timestamp); + } + } + + /** + * Returns the aggregation architecture-specific accumulator at the specified timestamp for the given group. + */ + protected abstract Accumulator getAccumulator(final Tuple group, final Timestamp timestamp); + + protected AggregateResult getResultRaw(final Tuple group, final Timestamp timestamp, final boolean lower) { + final TreeMap> entryMap = this.memory.get(group); + if (entryMap == null) { + return null; + } else { + CumulativeAggregate entry = null; + if (lower) { + final Entry> lowerEntry = entryMap + .lowerEntry(timestamp); + if (lowerEntry != null) { + entry = lowerEntry.getValue(); + } + } else { + entry = entryMap.get(timestamp); + } + if (entry == null) { + return null; + } else { + return entry.result; + } + } + } + + protected AggregateResult getResult(final Tuple group, final Timestamp timestamp, final boolean lower) { + final AggregateResult result = getResultRaw(group, timestamp, lower); + if (result == null) { + return NEUTRAL; + } else { + return result; + } + } + + protected AggregateResult getResult(final Tuple group, final Timestamp timestamp) { + return getResult(group, timestamp, false); + } + + protected void storeIfNotNeutral(final Tuple group, final Accumulator accumulator, final AggregateResult value, + final Timestamp timestamp) { + TreeMap> entryMap = this.memory.get(group); + if (operator.isNeutral(accumulator)) { + if (entryMap != null) { + entryMap.remove(timestamp); + if (entryMap.isEmpty()) { + this.memory.remove(group); + } + } + } else { + if (entryMap == null) { + entryMap = CollectionsFactory.createTreeMap(); + this.memory.put(group, entryMap); + } + entryMap.put(timestamp, new CumulativeAggregate<>(accumulator, value)); + } + } + + @Override + public Tuple getAggregateTuple(final Tuple group) { + return tupleFromAggregateResult(group, getResult(group, Timestamp.ZERO)); + } + + @Override + public AggregateResult getAggregateResult(final Tuple group) { + return getResult(group, Timestamp.ZERO); + } + + @Override + public Map> getAggregateResultTimeline(final Tuple group) { + final TreeMap> entryMap = this.memory.get(group); + if (entryMap == null) { + return Collections.emptyMap(); + } else { + final Map> result = CollectionsFactory.createMap(); + for (final Entry> entry : entryMap + .descendingMap().entrySet()) { + result.put(entry.getValue().result, Timelines.createFrom(entry.getKey())); + } + return result; + } + } + + @Override + public Map> getAggregateTupleTimeline(final Tuple group) { + final Map> resultTimelines = getAggregateResultTimeline(group); + return new GroupedMap>(group, resultTimelines, this.runtimeContext); + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/Disconnectable.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/Disconnectable.java new file mode 100644 index 00000000..7bbf74ea --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/Disconnectable.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.boundary; + +/** + * For objects that connect a RETE implementation to the underlying model. + * + * @author Gabor Bergmann + * + */ +public interface Disconnectable { + + /** + * Disconnects this rete engine component from the underlying model. Disconnecting enables the garbage collection + * mechanisms to dispose of the rete network. + */ + void disconnect(); + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/ExternalInputEnumeratorNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/ExternalInputEnumeratorNode.java new file mode 100644 index 00000000..51f89b52 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/ExternalInputEnumeratorNode.java @@ -0,0 +1,209 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.boundary; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContextListener; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.matcher.ReteEngine; +import tools.refinery.viatra.runtime.rete.network.Network; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.StandardNode; +import tools.refinery.viatra.runtime.rete.network.Supplier; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.timeless.BehaviorChangingMailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.timely.TimelyMailbox; +import tools.refinery.viatra.runtime.rete.remote.Address; + +/** + * An input node representing an enumerable extensional input relation and receiving external updates. + * + *

    + * Contains those tuples that are in the extensional relation identified by the input key, and also conform to the + * global seed (if any). + * + * @author Bergmann Gabor + * + */ +public class ExternalInputEnumeratorNode extends StandardNode + implements Disconnectable, Receiver, IQueryRuntimeContextListener { + + private IQueryRuntimeContext context = null; + private IInputKey inputKey; + private Tuple globalSeed; + private InputConnector inputConnector; + private Network network; + private Address myAddress; + private boolean parallelExecutionEnabled; + /** + * @since 1.6 + */ + protected final Mailbox mailbox; + private final IQueryBackendContext qBackendContext; + + public ExternalInputEnumeratorNode(ReteContainer reteContainer) { + super(reteContainer); + myAddress = Address.of(this); + network = reteContainer.getNetwork(); + inputConnector = network.getInputConnector(); + qBackendContext = network.getEngine().getBackendContext(); + mailbox = instantiateMailbox(); + reteContainer.registerClearable(mailbox); + } + + /** + * Instantiates the {@link Mailbox} of this receiver. Subclasses may override this method to provide their own + * mailbox implementation. + * + * @return the mailbox + * @since 2.0 + */ + protected Mailbox instantiateMailbox() { + if (this.reteContainer.isTimelyEvaluation()) { + return new TimelyMailbox(this, this.reteContainer); + } else { + return new BehaviorChangingMailbox(this, this.reteContainer); + } + } + + @Override + public Mailbox getMailbox() { + return this.mailbox; + } + + public void connectThroughContext(ReteEngine engine, IInputKey inputKey, Tuple globalSeed) { + this.inputKey = inputKey; + this.globalSeed = globalSeed; + setTag(inputKey); + + final IQueryRuntimeContext context = engine.getRuntimeContext(); + if (!context.getMetaContext().isEnumerable(inputKey)) + throw new IllegalArgumentException(this.getClass().getSimpleName() + + " only applicable for enumerable input keys; received instead " + inputKey); + + this.context = context; + this.parallelExecutionEnabled = engine.isParallelExecutionEnabled(); + + engine.addDisconnectable(this); + context.addUpdateListener(inputKey, globalSeed, this); + } + + @Override + public void disconnect() { + if (context != null) { // if connected + context.removeUpdateListener(inputKey, globalSeed, this); + context = null; + } + } + + /** + * @since 2.2 + */ + protected Iterable getTuplesInternal() { + Iterable tuples = null; + + if (context != null) { // if connected + if (globalSeed == null) { + tuples = context.enumerateTuples(inputKey, TupleMask.empty(inputKey.getArity()), + Tuples.staticArityFlatTupleOf()); + } else { + final TupleMask mask = TupleMask.fromNonNullIndices(globalSeed); + tuples = context.enumerateTuples(inputKey, mask, mask.transform(globalSeed)); + } + } + + return tuples; + } + + @Override + public void pullInto(final Collection collector, final boolean flush) { + final Iterable tuples = getTuplesInternal(); + if (tuples != null) { + for (final Tuple tuple : tuples) { + collector.add(tuple); + } + } + } + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + final Iterable tuples = getTuplesInternal(); + if (tuples != null) { + for (final Tuple tuple : tuples) { + collector.put(tuple, Timestamp.INSERT_AT_ZERO_TIMELINE); + } + } + } + + /* Update from runtime context */ + @Override + public void update(IInputKey key, Tuple update, boolean isInsertion) { + if (parallelExecutionEnabled) { + // send back to myself as an official external update, and then propagate it transparently + network.sendExternalUpdate(myAddress, direction(isInsertion), update); + } else { + if (qBackendContext.areUpdatesDelayed()) { + // post the update into the mailbox of the node + mailbox.postMessage(direction(isInsertion), update, Timestamp.ZERO); + } else { + // just propagate the input + update(direction(isInsertion), update, Timestamp.ZERO); + } + // if the the update method is called from within a delayed execution, + // the following invocation will be a no-op + network.waitForReteTermination(); + } + } + + private static Direction direction(boolean isInsertion) { + return isInsertion ? Direction.INSERT : Direction.DELETE; + } + + /* Self-addressed from network */ + @Override + public void update(Direction direction, Tuple updateElement, Timestamp timestamp) { + propagateUpdate(direction, updateElement, timestamp); + } + + @Override + public void appendParent(Supplier supplier) { + throw new UnsupportedOperationException("Input nodes can't have parents"); + } + + @Override + public void removeParent(Supplier supplier) { + throw new UnsupportedOperationException("Input nodes can't have parents"); + } + + @Override + public Collection getParents() { + return Collections.emptySet(); + } + + public IInputKey getInputKey() { + return inputKey; + } + + public Tuple getGlobalSeed() { + return globalSeed; + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/ExternalInputStatelessFilterNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/ExternalInputStatelessFilterNode.java new file mode 100644 index 00000000..57e06911 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/ExternalInputStatelessFilterNode.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.boundary; + +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.rete.matcher.ReteEngine; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.single.FilterNode; + +/** + * A filter node representing a (stateless, typically non-enumerable) extensional input relation. + * + *

    Contains those tuples of its parents, that (when transformed by a mask, if given) are present in the extensional relation identified by the input key. + * + * @author Bergmann Gabor + * + */ +public class ExternalInputStatelessFilterNode extends FilterNode implements Disconnectable { + + IQueryRuntimeContext context = null; + IInputKey inputKey; + private InputConnector inputConnector; + private TupleMask mask; + + public ExternalInputStatelessFilterNode(ReteContainer reteContainer, TupleMask mask) { + super(reteContainer); + this.mask = mask; + this.inputConnector = reteContainer.getNetwork().getInputConnector(); + } + + @Override + public boolean check(Tuple ps) { + if (mask != null) + ps = mask.transform(ps); + return context.containsTuple(inputKey, ps); + } + + + public void connectThroughContext(ReteEngine engine, IInputKey inputKey) { + this.inputKey = inputKey; + setTag(inputKey); + + final IQueryRuntimeContext context = engine.getRuntimeContext(); + if (!context.getMetaContext().isStateless(inputKey)) + throw new IllegalArgumentException( + this.getClass().getSimpleName() + + " only applicable for stateless input keys; received instead " + + inputKey); + + this.context = context; + + engine.addDisconnectable(this); + } + + @Override + public void disconnect() { + this.context = null; + } +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/InputConnector.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/InputConnector.java new file mode 100644 index 00000000..c044850f --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/InputConnector.java @@ -0,0 +1,208 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.boundary; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.stream.Stream; + +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.rete.network.Network; +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.recipes.InputFilterRecipe; +import tools.refinery.viatra.runtime.rete.recipes.InputRecipe; +import tools.refinery.viatra.runtime.rete.remote.Address; + +/** + * A class responsible for connecting input nodes to the runtime context. + * + * @author Bergmann Gabor + * + */ +public final class InputConnector { + Network network; + + private Map>> externalInputRoots = CollectionsFactory.createMap(); + +// /* +// * arity:1 used as simple entity constraints label is the object representing the type null label means all entities +// * regardless of type (global supertype), if allowed +// */ +// protected Map> unaryRoots = CollectionsFactory.getMap(); +// /* +// * arity:3 (rel, from, to) used as VPM relation constraints null label means all relations regardless of type +// * (global supertype) +// */ +// protected Map> ternaryEdgeRoots = CollectionsFactory.getMap(); +// /* +// * arity:2 (from, to) not used over VPM; can be used as EMF references for instance label is the object representing +// * the type null label means all entities regardless of type if allowed (global supertype), if allowed +// */ +// protected Map> binaryEdgeRoots = CollectionsFactory.getMap(); +// +// protected Address containmentRoot = null; +// protected Address containmentTransitiveRoot = null; +// protected Address instantiationRoot = null; +// protected Address instantiationTransitiveRoot = null; +// protected Address generalizationRoot = null; +// protected Address generalizationTransitiveRoot = null; + + + public InputConnector(Network network) { + super(); + this.network = network; + } + + + public Network getNetwork() { + return network; + } + + + /** + * Connects a given input filter node to the external input source. + */ + public void connectInputFilter(InputFilterRecipe recipe, Node freshNode) { + final ExternalInputStatelessFilterNode inputNode = (ExternalInputStatelessFilterNode)freshNode; + + IInputKey inputKey = (IInputKey) recipe.getInputKey(); + inputNode.connectThroughContext(network.getEngine(), inputKey); + } + + + /** + * Connects a given input enumerator node to the external input source. + */ + public void connectInput(InputRecipe recipe, Node freshNode) { + final ExternalInputEnumeratorNode inputNode = (ExternalInputEnumeratorNode)freshNode; + + IInputKey inputKey = (IInputKey) recipe.getInputKey(); + Tuple seed = nopSeed(inputKey); // no preseeding as of now + final Address freshAddress = Address.of(inputNode); + externalInputRoots.computeIfAbsent(inputKey, k -> CollectionsFactory.createMap()).put(seed, freshAddress); + inputNode.connectThroughContext(network.getEngine(), inputKey, seed); + +// final Address freshAddress = Address.of((Tunnel)freshNode); +// if (recipe instanceof TypeInputRecipe) { +// final Object typeKey = ((TypeInputRecipe) recipe).getTypeKey(); +// +// if (recipe instanceof UnaryInputRecipe) { +// unaryRoots.put(typeKey, freshAddress); +// new EntityFeeder(freshAddress, this, typeKey).feed(); +//// if (typeObject != null && generalizationQueryDirection == GeneralizationQueryDirection.BOTH) { +//// Collection subTypes = context.enumerateDirectUnarySubtypes(typeObject); +//// +//// for (Object subType : subTypes) { +//// Address subRoot = accessUnaryRoot(subType); +//// network.connectRemoteNodes(subRoot, tn, true); +//// } +//// } +// } else if (recipe instanceof BinaryInputRecipe) { +// binaryEdgeRoots.put(typeKey, freshAddress); +// externalInputRoots.put(rowKey, columnKey, freshAddress); +// new ReferenceFeeder(freshAddress, this, typeKey).feed(); +// // if (typeObject != null && generalizationQueryDirection == GeneralizationQueryDirection.BOTH) { +// // Collection subTypes = context.enumerateDirectTernaryEdgeSubtypes(typeObject); +// // +// // for (Object subType : subTypes) { +// // Address subRoot = accessTernaryEdgeRoot(subType); +// // network.connectRemoteNodes(subRoot, tn, true); +// // } +// // } +// } +// +// +// } + + } + +// /** +// * fetches the entity Root node under specified label; returns null if it doesn't exist yet +// */ +// public Address getUnaryRoot(Object label) { +// return unaryRoots.get(label); +// } +// +// public Collection> getAllUnaryRoots() { +// return unaryRoots.values(); +// } +// +// /** +// * fetches the relation Root node under specified label; returns null if it doesn't exist yet +// */ +// public Address getTernaryEdgeRoot(Object label) { +// return ternaryEdgeRoots.get(label); +// } +// +// public Collection> getAllTernaryEdgeRoots() { +// return ternaryEdgeRoots.values(); +// } +// +// /** +// * fetches the reference Root node under specified label; returns null if it doesn't exist yet +// */ +// public Address getBinaryEdgeRoot(Object label) { +// return binaryEdgeRoots.get(label); +// } +// +// public Collection> getAllBinaryEdgeRoots() { +// return binaryEdgeRoots.values(); +// } +// +// +// public Address getContainmentRoot() { +// return containmentRoot; +// } +// +// +// public Address getContainmentTransitiveRoot() { +// return containmentTransitiveRoot; +// } +// +// +// public Address getInstantiationRoot() { +// return instantiationRoot; +// } +// +// +// public Address getInstantiationTransitiveRoot() { +// return instantiationTransitiveRoot; +// } +// +// +// public Address getGeneralizationRoot() { +// return generalizationRoot; +// } + + + public Stream> getAllExternalInputNodes() { + return externalInputRoots.values().stream().flatMap(map -> map.values().stream()); + } + public Collection> getAllExternalInputNodesForKey(IInputKey inputKey) { + return externalInputRoots.getOrDefault(inputKey, Collections.emptyMap()).values(); + } + public Address getExternalInputNodeForKeyUnseeded(IInputKey inputKey) { + return externalInputRoots.getOrDefault(inputKey, Collections.emptyMap()).get(nopSeed(inputKey)); + } + public Address getExternalInputNode(IInputKey inputKey, Tuple seed) { + if (seed == null) seed = nopSeed(inputKey); + return externalInputRoots.getOrDefault(inputKey, Collections.emptyMap()).get(seed); + } + + + Tuple nopSeed(IInputKey inputKey) { + return Tuples.flatTupleOf(new Object[inputKey.getArity()]); + } + + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/ReteBoundary.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/ReteBoundary.java new file mode 100644 index 00000000..fe9c795e --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/boundary/ReteBoundary.java @@ -0,0 +1,551 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.boundary; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; +import tools.refinery.viatra.runtime.matchers.planning.SubPlan; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.matcher.ReteEngine; +import tools.refinery.viatra.runtime.rete.network.Network; +import tools.refinery.viatra.runtime.rete.network.ProductionNode; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.Supplier; +import tools.refinery.viatra.runtime.rete.remote.Address; +import tools.refinery.viatra.runtime.rete.traceability.CompiledQuery; +import tools.refinery.viatra.runtime.rete.traceability.RecipeTraceInfo; + +/** + * Responsible for the storage, maintenance and communication of the nodes of the network that are accessible form the + * outside for various reasons. + * + * @author Gabor Bergmann + * + *

    TODO: should eventually be merged into {@link InputConnector} and deleted + * + */ +public class ReteBoundary /*implements IPatternMatcherRuntimeContextListener*/ { + + protected ReteEngine engine; + protected Network network; + protected ReteContainer headContainer; + + public ReteContainer getHeadContainer() { + return headContainer; + } + + protected final InputConnector inputConnector; + + + protected Map> subplanToAddressMapping; + + + /** + * SubPlans of parent nodes that have the key node as their child. For RETE --> SubPlan traceability, mainly at production + * nodes. + */ + protected Map, Set> parentPlansOfReceiver; + + /** + * Prerequisite: engine has its network and framework fields initialized + */ + public ReteBoundary(ReteEngine engine) { + super(); + this.engine = engine; + this.network = engine.getReteNet(); + this.headContainer = network.getHeadContainer(); + inputConnector = network.getInputConnector(); + + this.parentPlansOfReceiver = CollectionsFactory.createMap(); + + // productionsScoped = new HashMap,Address>>(); + subplanToAddressMapping = CollectionsFactory.createMap(); + + } + + public Collection getAllProductionNodes() { + return engine.getCompiler().getCachedCompiledQueries().values(); + } + +// /** +// * accesses the entity Root node under specified label; creates the node if it doesn't exist yet +// */ +// public Address accessUnaryRoot(Object typeObject) { +// Address tn; +// tn = unaryRoots.get(typeObject); +// if (tn == null) { +// tn = headContainer.getProvisioner().newUniquenessEnforcerNode(1, typeObject); +// unaryRoots.put(typeObject, tn); +// +// new EntityFeeder(tn, context, network, this, typeObject).feed(); +// +// if (typeObject != null && generalizationQueryDirection == GeneralizationQueryDirection.BOTH) { +// Collection subTypes = context.enumerateDirectUnarySubtypes(typeObject); +// +// for (Object subType : subTypes) { +// Address subRoot = accessUnaryRoot(subType); +// network.connectRemoteNodes(subRoot, tn, true); +// } +// } +// +// } +// return tn; +// } +// +// /** +// * accesses the relation Root node under specified label; creates the node if it doesn't exist yet +// */ +// public Address accessTernaryEdgeRoot(Object typeObject) { +// Address tn; +// tn = ternaryEdgeRoots.get(typeObject); +// if (tn == null) { +// tn = headContainer.getProvisioner().newUniquenessEnforcerNode(3, typeObject); +// ternaryEdgeRoots.put(typeObject, tn); +// +// new RelationFeeder(tn, context, network, this, typeObject).feed(); +// +// if (typeObject != null && generalizationQueryDirection == GeneralizationQueryDirection.BOTH) { +// Collection subTypes = context.enumerateDirectTernaryEdgeSubtypes(typeObject); +// +// for (Object subType : subTypes) { +// Address subRoot = accessTernaryEdgeRoot(subType); +// network.connectRemoteNodes(subRoot, tn, true); +// } +// } +// } +// return tn; +// } +// +// /** +// * accesses the reference Root node under specified label; creates the node if it doesn't exist yet +// */ +// public Address accessBinaryEdgeRoot(Object typeObject) { +// Address tn; +// tn = binaryEdgeRoots.get(typeObject); +// if (tn == null) { +// tn = headContainer.getProvisioner().newUniquenessEnforcerNode(2, typeObject); +// binaryEdgeRoots.put(typeObject, tn); +// +// new ReferenceFeeder(tn, context, network, this, typeObject).feed(); +// +// if (typeObject != null && generalizationQueryDirection == GeneralizationQueryDirection.BOTH) { +// Collection subTypes = context.enumerateDirectBinaryEdgeSubtypes(typeObject); +// +// for (Object subType : subTypes) { +// Address subRoot = accessBinaryEdgeRoot(subType); +// network.connectRemoteNodes(subRoot, tn, true); +// } +// } +// } +// return tn; +// } +// +// /** +// * accesses the special direct containment relation Root node; creates the node if it doesn't exist yet +// */ +// public Address accessContainmentRoot() { +// if (containmentRoot == null) { +// // containment: relation quasi-type +// containmentRoot = headContainer.getProvisioner().newUniquenessEnforcerNode(2, "$containment"); +// +// new ContainmentFeeder(containmentRoot, context, network, this).feed(); +// } +// return containmentRoot; +// } +// +// /** +// * accesses the special transitive containment relation Root node; creates the node if it doesn't exist yet +// */ +// public Address accessContainmentTransitiveRoot() { +// if (containmentTransitiveRoot == null) { +// // transitive containment: derived +// Address containmentTransitiveRoot = headContainer.getProvisioner().newUniquenessEnforcerNode( +// 2, "$containmentTransitive"); +// network.connectRemoteNodes(accessContainmentRoot(), containmentTransitiveRoot, true); +// +// final int[] actLI = { 1 }; +// final int arcLIw = 2; +// final int[] actRI = { 0 }; +// final int arcRIw = 2; +// Address jPrimarySlot = headContainer.getProvisioner().accessProjectionIndexer( +// accessContainmentRoot(), new TupleMask(actLI, arcLIw)); +// Address jSecondarySlot = headContainer.getProvisioner().accessProjectionIndexer( +// containmentTransitiveRoot, new TupleMask(actRI, arcRIw)); +// +// final int[] actRIcomp = { 1 }; +// final int arcRIwcomp = 2; +// TupleMask complementerMask = new TupleMask(actRIcomp, arcRIwcomp); +// +// Address andCT = headContainer.getProvisioner().accessJoinNode(jPrimarySlot, jSecondarySlot, +// complementerMask); +// +// final int[] mask = { 0, 2 }; +// final int maskw = 3; +// Address tr = headContainer.getProvisioner().accessTrimmerNode(andCT, new TupleMask(mask, maskw)); +// network.connectRemoteNodes(tr, containmentTransitiveRoot, true); +// +// this.containmentTransitiveRoot = containmentTransitiveRoot; // cast +// // back +// // to +// // Supplier +// } +// return containmentTransitiveRoot; +// } +// +// /** +// * accesses the special instantiation relation Root node; creates the node if it doesn't exist yet +// */ +// public Address accessInstantiationRoot() { +// if (instantiationRoot == null) { +// // instantiation: relation quasi-type +// instantiationRoot = headContainer.getProvisioner().newUniquenessEnforcerNode(2, "$instantiation"); +// +// new InstantiationFeeder(instantiationRoot, context, network, this).feed(); +// } +// return instantiationRoot; +// } +// +// /** +// * accesses the special transitive instantiation relation Root node; creates the node if it doesn't exist yet +// * InstantiationTransitive = Instantiation o (Generalization)^* +// */ +// public Address accessInstantiationTransitiveRoot() { +// if (instantiationTransitiveRoot == null) { +// // transitive instantiation: derived +// Address instantiationTransitiveRoot = headContainer.getProvisioner() +// .newUniquenessEnforcerNode(2, "$instantiationTransitive"); +// network.connectRemoteNodes(accessInstantiationRoot(), instantiationTransitiveRoot, true); +// +// final int[] actLI = { 1 }; +// final int arcLIw = 2; +// final int[] actRI = { 0 }; +// final int arcRIw = 2; +// Address jPrimarySlot = headContainer.getProvisioner().accessProjectionIndexer( +// accessGeneralizationRoot(), new TupleMask(actLI, arcLIw)); +// Address jSecondarySlot = headContainer.getProvisioner().accessProjectionIndexer( +// instantiationTransitiveRoot, new TupleMask(actRI, arcRIw)); +// +// final int[] actRIcomp = { 1 }; +// final int arcRIwcomp = 2; +// TupleMask complementerMask = new TupleMask(actRIcomp, arcRIwcomp); +// +// Address andCT = headContainer.getProvisioner().accessJoinNode(jPrimarySlot, jSecondarySlot, +// complementerMask); +// +// final int[] mask = { 0, 2 }; +// final int maskw = 3; +// Address tr = headContainer.getProvisioner().accessTrimmerNode(andCT, +// new TupleMask(mask, maskw)); +// network.connectRemoteNodes(tr, instantiationTransitiveRoot, true); +// +// this.instantiationTransitiveRoot = instantiationTransitiveRoot; // cast +// // back +// // to +// // Supplier +// } +// return instantiationTransitiveRoot; +// } +// +// /** +// * accesses the special generalization relation Root node; creates the node if it doesn't exist yet +// */ +// public Address accessGeneralizationRoot() { +// if (generalizationRoot == null) { +// // generalization: relation quasi-type +// generalizationRoot = headContainer.getProvisioner().newUniquenessEnforcerNode(2, "$generalization"); +// +// new GeneralizationFeeder(generalizationRoot, context, network, this).feed(); +// } +// return generalizationRoot; +// } +// +// /** +// * accesses the special transitive containment relation Root node; creates the node if it doesn't exist yet +// */ +// public Address accessGeneralizationTransitiveRoot() { +// if (generalizationTransitiveRoot == null) { +// // transitive generalization: derived +// Address generalizationTransitiveRoot = headContainer.getProvisioner() +// .newUniquenessEnforcerNode(2, "$generalizationTransitive"); +// network.connectRemoteNodes(accessGeneralizationRoot(), generalizationTransitiveRoot, true); +// +// final int[] actLI = { 1 }; +// final int arcLIw = 2; +// final int[] actRI = { 0 }; +// final int arcRIw = 2; +// Address jPrimarySlot = headContainer.getProvisioner().accessProjectionIndexer( +// accessGeneralizationRoot(), new TupleMask(actLI, arcLIw)); +// Address jSecondarySlot = headContainer.getProvisioner().accessProjectionIndexer( +// generalizationTransitiveRoot, new TupleMask(actRI, arcRIw)); +// +// final int[] actRIcomp = { 1 }; +// final int arcRIwcomp = 2; +// TupleMask complementerMask = new TupleMask(actRIcomp, arcRIwcomp); +// +// Address andCT = headContainer.getProvisioner().accessJoinNode(jPrimarySlot, jSecondarySlot, +// complementerMask); +// +// final int[] mask = { 0, 2 }; +// final int maskw = 3; +// Address tr = headContainer.getProvisioner().accessTrimmerNode(andCT, new TupleMask(mask, maskw)); +// network.connectRemoteNodes(tr, generalizationTransitiveRoot, true); +// +// this.generalizationTransitiveRoot = generalizationTransitiveRoot; // cast +// // back +// // to +// // Supplier +// } +// return generalizationTransitiveRoot; +// } + + // /** + // * Registers and publishes a supplier under specified label. + // */ + // public void publishSupplier(Supplier s, Object label) + // { + // publishedSuppliers.put(label, s); + // } + // + // /** + // * fetches the production node under specified label; + // * returns null if it doesn't exist yet + // */ + // public Production getProductionNode(Object label) + // { + // return productions.get(label); + // } + // + // /** + // * fetches the published supplier under specified label; + // * returns null if it doesn't exist yet + // */ + // public Supplier getPublishedSupplier(Object label) + // { + // return publishedSuppliers.get(label); + // } + + /** + * accesses the production node for specified pattern; builds pattern matcher if it doesn't exist yet + * @throws ViatraQueryRuntimeException + */ + public synchronized RecipeTraceInfo accessProductionTrace(PQuery query) + { + final CompiledQuery compiled = engine.getCompiler().getCompiledForm(query); + return compiled; +// RecipeTraceInfo pn; +// pn = queryPlans.get(query); +// if (pn == null) { +// pn = construct(query); +// TODO handle recursion by reinterpret-RecipeTrace +// queryPlans.put(query, pn); +// if (pn == null) { +// String[] args = { query.toString() }; +// throw new RetePatternBuildException("Unsuccessful planning of RETE construction recipe for query {1}", +// args, "Could not create RETE recipe plan.", query); +// } +// } +// return pn; + } + /** + * accesses the production node for specified pattern; builds pattern matcher if it doesn't exist yet + * @throws ViatraQueryRuntimeException + */ + public synchronized Address accessProductionNode(PQuery query) { + final RecipeTraceInfo productionTrace = accessProductionTrace(query); + return (Address) headContainer.getProvisioner().getOrCreateNodeByRecipe(productionTrace); + } + +// /** +// * creates the production node for the specified pattern Contract: only call from the builder (through Buildable) +// * responsible for building this pattern +// * +// * @throws PatternMatcherCompileTimeException +// * if production node is already created +// */ +// public synchronized Address createProductionInternal(PQuery gtPattern) +// throws QueryPlannerException { +// if (queryPlans.containsKey(gtPattern)) { +// String[] args = { gtPattern.toString() }; +// throw new RetePatternBuildException("Multiple creation attempts of production node for {1}", args, +// "Duplicate RETE production node.", gtPattern); +// } +// +// Map posMapping = engine.getBuilder().getPosMapping(gtPattern); +// Address pn = headContainer.getProvisioner().newProductionNode(posMapping, gtPattern); +// queryPlans.put(gtPattern, pn); +// context.reportPatternDependency(gtPattern); +// +// return pn; +// } + + // /** + // * accesses the production node for specified pattern and scope map; creates the node if + // * it doesn't exist yet + // */ + // public synchronized Address accessProductionScoped( + // GTPattern gtPattern, Map additionalScopeMap) throws PatternMatcherCompileTimeException { + // if (additionalScopeMap.isEmpty()) return accessProduction(gtPattern); + // + // Address pn; + // + // Map, Address> scopes = productionsScoped.get(gtPattern); + // if (scopes == null) { + // scopes = new HashMap, Address>(); + // productionsScoped.put(gtPattern, scopes); + // } + // + // pn = scopes.get(additionalScopeMap); + // if (pn == null) { + // Address unscopedProduction = accessProduction(gtPattern); + // + // HashMap posMapping = headContainer.resolveLocal(unscopedProduction).getPosMapping(); + // pn = headContainer.getLibrary().newProductionNode(posMapping); + // scopes.put(additionalScopeMap, pn); + // + // constructScoper(unscopedProduction, additionalScopeMap, pn); + // } + // return pn; + // } + + // protected void constructScoper( + // Address unscopedProduction, + // Map additionalScopeMap, + // Address production) + // throws PatternMatcherCompileTimeException { + // engine.reteNet.waitForReteTermination(); + // engine.builder.constructScoper(unscopedProduction, additionalScopeMap, production); + // } + + // /** + // * Invalidates the subnet constructed for the recognition of a given + // pattern. + // * The pattern matcher will have to be rebuilt. + // * @param gtPattern the pattern whose matcher subnet should be invalidated + // */ + // public void invalidatePattern(GTPattern gtPattern) { + // Production production = null; + // try { + // production = accessProduction(gtPattern); + // } catch (PatternMatcherCompileTimeException e) { + // // this should not occur here, since we already have a production node + // e.printStackTrace(); + // } + // + // production.tearOff(); + // //production.setDirty(true); + // } + + // updaters for change notification + // if the corresponding rete input isn't created yet, call is ignored + + private static Direction direction(boolean isInsertion) { + return isInsertion ? Direction.INSERT : Direction.DELETE; + } + +// @Override +// public void updateUnary(boolean isInsertion, Object entity, Object typeObject) { +// Address root = inputConnector.getUnaryRoot(typeObject); +// if (root != null) { +// network.sendExternalUpdate(root, direction(isInsertion), new FlatTuple(inputConnector.wrapElement(entity))); +// if (!engine.isParallelExecutionEnabled()) +// network.waitForReteTermination(); +// } +// if (typeObject != null && generalizationQueryDirection == GeneralizationQueryDirection.SUPERTYPE_ONLY) { +// for (Object superType : context.enumerateDirectUnarySupertypes(typeObject)) { +// updateUnary(isInsertion, entity, superType); +// } +// } +// } +// +// @Override +// public void updateTernaryEdge(boolean isInsertion, Object relation, Object from, Object to, Object typeObject) { +// Address root = inputConnector.getTernaryEdgeRoot(typeObject); +// if (root != null) { +// network.sendExternalUpdate(root, direction(isInsertion), new FlatTuple(inputConnector.wrapElement(relation), inputConnector.wrapElement(from), +// inputConnector.wrapElement(to))); +// if (!engine.isParallelExecutionEnabled()) +// network.waitForReteTermination(); +// } +// if (typeObject != null && generalizationQueryDirection == GeneralizationQueryDirection.SUPERTYPE_ONLY) { +// for (Object superType : context.enumerateDirectTernaryEdgeSupertypes(typeObject)) { +// updateTernaryEdge(isInsertion, relation, from, to, superType); +// } +// } +// } +// @Override +// public void updateBinaryEdge(boolean isInsertion, Object from, Object to, Object typeObject) { +// Address root = inputConnector.getBinaryEdgeRoot(typeObject); +// if (root != null) { +// network.sendExternalUpdate(root, direction(isInsertion), new FlatTuple(inputConnector.wrapElement(from), inputConnector.wrapElement(to))); +// if (!engine.isParallelExecutionEnabled()) +// network.waitForReteTermination(); +// } +// if (typeObject != null && generalizationQueryDirection == GeneralizationQueryDirection.SUPERTYPE_ONLY) { +// for (Object superType : context.enumerateDirectBinaryEdgeSupertypes(typeObject)) { +// updateBinaryEdge(isInsertion, from, to, superType); +// } +// } +// } +// +// @Override +// public void updateContainment(boolean isInsertion, Object container, Object element) { +// final Address containmentRoot = inputConnector.getContainmentRoot(); +// if (containmentRoot != null) { +// network.sendExternalUpdate(containmentRoot, direction(isInsertion), new FlatTuple(inputConnector.wrapElement(container), +// inputConnector.wrapElement(element))); +// if (!engine.isParallelExecutionEnabled()) +// network.waitForReteTermination(); +// } +// } +// +// @Override +// public void updateInstantiation(boolean isInsertion, Object parent, Object child) { +// final Address instantiationRoot = inputConnector.getInstantiationRoot(); +// if (instantiationRoot != null) { +// network.sendExternalUpdate(instantiationRoot, direction(isInsertion), new FlatTuple(inputConnector.wrapElement(parent), +// inputConnector.wrapElement(child))); +// if (!engine.isParallelExecutionEnabled()) +// network.waitForReteTermination(); +// } +// } +// +// @Override +// public void updateGeneralization(boolean isInsertion, Object parent, Object child) { +// final Address generalizationRoot = inputConnector.getGeneralizationRoot(); +// if (generalizationRoot != null) { +// network.sendExternalUpdate(generalizationRoot, direction(isInsertion), new FlatTuple(inputConnector.wrapElement(parent), +// inputConnector.wrapElement(child))); +// if (!engine.isParallelExecutionEnabled()) +// network.waitForReteTermination(); +// } +// } + + // no wrapping needed! + public void notifyEvaluator(Address receiver, Tuple tuple) { + network.sendExternalUpdate(receiver, Direction.INSERT, tuple); + if (!engine.isParallelExecutionEnabled()) + network.waitForReteTermination(); + } + + public void mapPlanToAddress(SubPlan plan, Address handle) { + subplanToAddressMapping.put(plan, handle); + } + + public Address getAddress(SubPlan plan) { + return subplanToAddressMapping.get(plan); + } +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/RetePatternBuildException.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/RetePatternBuildException.java new file mode 100644 index 00000000..bd1b219e --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/RetePatternBuildException.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2004-2009 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.construction; + +import tools.refinery.viatra.runtime.matchers.planning.QueryProcessingException; + +/** + * A problem has occurred during the construction of the RETE net. + * + * @author Gabor Bergmann + * + */ +public class RetePatternBuildException extends QueryProcessingException { + + private static final long serialVersionUID = 6966585498204577548L; + + /** + * @param message + * The template of the exception message + * @param context + * The data elements to be used to instantiate the template. Can be null if no context parameter is + * defined + * @param patternDescription + * the PatternDescription where the exception occurred + */ + public RetePatternBuildException(String message, String[] context, String shortMessage, Object patternDescription) { + super(message, context, shortMessage, patternDescription); + } + + /** + * @param message + * The template of the exception message + * @param context + * The data elements to be used to instantiate the template. Can be null if no context parameter is + * defined + * @param patternDescription + * the PatternDescription where the exception occurred + */ + public RetePatternBuildException(String message, String[] context, String shortMessage, Object patternDescription, + Throwable cause) { + super(message, context, shortMessage, patternDescription, cause); + } +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/basiclinear/BasicLinearLayout.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/basiclinear/BasicLinearLayout.java new file mode 100644 index 00000000..bd22e1a0 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/basiclinear/BasicLinearLayout.java @@ -0,0 +1,171 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.construction.basiclinear; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Set; + +import org.apache.log4j.Logger; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendHintProvider; +import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.planning.IQueryPlannerStrategy; +import tools.refinery.viatra.runtime.matchers.planning.SubPlan; +import tools.refinery.viatra.runtime.matchers.planning.SubPlanFactory; +import tools.refinery.viatra.runtime.matchers.planning.helpers.BuildHelper; +import tools.refinery.viatra.runtime.matchers.planning.operations.PApply; +import tools.refinery.viatra.runtime.matchers.planning.operations.PProject; +import tools.refinery.viatra.runtime.matchers.planning.operations.PStart; +import tools.refinery.viatra.runtime.matchers.psystem.DeferredPConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.VariableDeferredPConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.Equality; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.ExportedParameter; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.ExpressionEvaluation; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.rete.construction.RetePatternBuildException; + +/** + * Basic layout that builds a linear RETE net based on a heuristic ordering of constraints. + * + * @author Gabor Bergmann + * + */ +public class BasicLinearLayout implements IQueryPlannerStrategy { + + //SubPlanProcessor planProcessor = new SubPlanProcessor(); + + private IQueryBackendHintProvider hintProvider; + private IQueryBackendContext bContext; + /** + * @param bContext + * @since 1.5 + */ + public BasicLinearLayout(IQueryBackendContext bContext) { + this.bContext = bContext; + this.hintProvider = bContext.getHintProvider(); + } + + @Override + public SubPlan plan(final PBody pSystem, Logger logger, IQueryMetaContext context) { + SubPlanFactory planFactory = new SubPlanFactory(pSystem); + PQuery query = pSystem.getPattern(); + //planProcessor.setCompiler(compiler); + try { + logger.debug(String.format( + "%s: patternbody build started for %s", + getClass().getSimpleName(), + query.getFullyQualifiedName())); + + // STARTING THE LINE + SubPlan plan = planFactory.createSubPlan(new PStart()); + + Set pQueue = CollectionsFactory.createSet(pSystem.getConstraints()); + + // MAIN LOOP + while (!pQueue.isEmpty()) { + PConstraint pConstraint = Collections.min(pQueue, + new OrderingHeuristics(plan, context)); // pQueue.iterator().next(); + pQueue.remove(pConstraint); + + // if we have no better option than an unready deferred constraint, raise error + if (pConstraint instanceof DeferredPConstraint) { + final DeferredPConstraint deferred = (DeferredPConstraint) pConstraint; + if (!deferred.isReadyAt(plan, context)) { + raiseForeverDeferredError(deferred, plan, context); + } + } + // TODO integrate the check above in SubPlan / POperation?? + + // replace incumbent plan with its child + plan = planFactory.createSubPlan(new PApply(pConstraint), plan); + } + + // PROJECT TO PARAMETERS + SubPlan finalPlan = planFactory.createSubPlan(new PProject(pSystem.getSymbolicParameterVariables()), plan); + + // FINAL CHECK, whether all exported variables are present + all constraint satisfied + BuildHelper.finalCheck(pSystem, finalPlan, context); + // TODO integrate the check above in SubPlan / POperation + + logger.debug(String.format( + "%s: patternbody query plan concluded for %s as: %s", + getClass().getSimpleName(), + query.getFullyQualifiedName(), + finalPlan.toLongString())); + + return finalPlan; + + } catch (RetePatternBuildException ex) { + ex.setPatternDescription(query); + throw ex; + } + } + + /** + * Called when the constraint is not ready, but cannot be deferred further. + * + * @param plan + * @throws RetePatternBuildException + * to indicate the error in detail. + */ + private void raiseForeverDeferredError(DeferredPConstraint constraint, SubPlan plan, IQueryMetaContext context) { + if (constraint instanceof Equality) { + raiseForeverDeferredError((Equality)constraint, plan, context); + } else if (constraint instanceof ExportedParameter) { + raiseForeverDeferredError((ExportedParameter)constraint, plan, context); + } else if (constraint instanceof ExpressionEvaluation) { + raiseForeverDeferredError((ExpressionEvaluation)constraint, plan, context); + } else if (constraint instanceof VariableDeferredPConstraint) { + raiseForeverDeferredError(constraint, plan, context); + } + } + + private void raiseForeverDeferredError(Equality constraint, SubPlan plan, IQueryMetaContext context) { + String[] args = { constraint.getWho().toString(), constraint.getWithWhom().toString() }; + String msg = "Cannot express equality of variables {1} and {2} if neither of them is deducable."; + String shortMsg = "Equality between undeducible variables."; + throw new RetePatternBuildException(msg, args, shortMsg, null); + } + private void raiseForeverDeferredError(ExportedParameter constraint, SubPlan plan, IQueryMetaContext context) { + String[] args = { constraint.getParameterName() }; + String msg = "Pattern Graph Search terminated incompletely: " + + "exported pattern variable {1} could not be determined based on the pattern constraints. " + + "HINT: certain constructs (e.g. negative patterns or check expressions) cannot output symbolic parameters."; + String shortMsg = "Could not deduce value of parameter"; + throw new RetePatternBuildException(msg, args, shortMsg, null); + } + private void raiseForeverDeferredError(ExpressionEvaluation constraint, SubPlan plan, IQueryMetaContext context) { + if (constraint.checkTypeSafety(plan, context) == null) { + raiseForeverDeferredError(constraint, plan); + } else { + String[] args = { toString(), constraint.checkTypeSafety(plan, context).toString() }; + String msg = "The checking of pattern constraint {1} cannot be deferred further, but variable {2} is still not type safe. " + + "HINT: the incremental matcher is not an equation solver, please make sure that all variable values are deducible."; + String shortMsg = "Could not check all constraints due to undeducible type restrictions"; + throw new RetePatternBuildException(msg, args, shortMsg, null); + } + } + private void raiseForeverDeferredError(VariableDeferredPConstraint constraint, SubPlan plan) { + Set missing = CollectionsFactory.createSet(constraint.getDeferringVariables());//new HashSet(getDeferringVariables()); + missing.removeAll(plan.getVisibleVariables()); + String[] args = { toString(), Arrays.toString(missing.toArray()) }; + String msg = "The checking of pattern constraint {1} requires the values of variables {2}, but it cannot be deferred further. " + + "HINT: the incremental matcher is not an equation solver, please make sure that all variable values are deducible."; + String shortMsg = "Could not check all constraints due to undeducible variables"; + throw new RetePatternBuildException(msg, args, shortMsg, null); + } + + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/basiclinear/OrderingHeuristics.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/basiclinear/OrderingHeuristics.java new file mode 100644 index 00000000..2b4e8890 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/basiclinear/OrderingHeuristics.java @@ -0,0 +1,90 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.construction.basiclinear; + +import java.util.Comparator; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.planning.SubPlan; +import tools.refinery.viatra.runtime.matchers.psystem.DeferredPConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.EnumerablePConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.ConstantValue; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.rete.util.OrderingCompareAgent; + +/** + * @author Gabor Bergmann + * + */ +public class OrderingHeuristics implements Comparator { + private SubPlan plan; + private IQueryMetaContext context; + + public OrderingHeuristics(SubPlan plan, IQueryMetaContext context) { + super(); + this.plan = plan; + this.context = context; + } + + @Override + public int compare(PConstraint o1, PConstraint o2) { + return new OrderingCompareAgent(o1, o2) { + @Override + protected void doCompare() { + boolean temp = consider(preferTrue(isConstant(a), isConstant(b))) + && consider(preferTrue(isReady(a), isReady(b))); + if (!temp) + return; + + Set bound1 = boundVariables(a); + Set bound2 = boundVariables(b); + swallowBoolean(temp && consider(preferTrue(isBound(a, bound1), isBound(b, bound2))) + && consider(preferMore(degreeBound(a, bound1), degreeBound(b, bound2))) + && consider(preferLess(degreeFree(a, bound1), degreeFree(b, bound2))) + + // tie breaking + && consider(preferLess(a.getMonotonousID(), b.getMonotonousID())) // this is hopefully deterministic + && consider(preferLess(System.identityHashCode(a), System.identityHashCode(b)))); + } + }.compare(); + } + + boolean isConstant(PConstraint o) { + return (o instanceof ConstantValue); + } + + boolean isReady(PConstraint o) { + return (o instanceof EnumerablePConstraint) + || (o instanceof DeferredPConstraint && ((DeferredPConstraint) o) + .isReadyAt(plan, context)); + } + + Set boundVariables(PConstraint o) { + Set boundVariables = CollectionsFactory.createSet(o.getAffectedVariables()); + boundVariables.retainAll(plan.getVisibleVariables()); + return boundVariables; + } + + boolean isBound(PConstraint o, Set boundVariables) { + return boundVariables.size() == o.getAffectedVariables().size(); + } + + int degreeBound(PConstraint o, Set boundVariables) { + return boundVariables.size(); + } + + int degreeFree(PConstraint o, Set boundVariables) { + return o.getAffectedVariables().size() - boundVariables.size(); + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/plancompiler/CompilerHelper.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/plancompiler/CompilerHelper.java new file mode 100644 index 00000000..da2fb432 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/plancompiler/CompilerHelper.java @@ -0,0 +1,390 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.construction.plancompiler; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.IPosetComparator; +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.planning.SubPlan; +import tools.refinery.viatra.runtime.matchers.planning.helpers.TypeHelper; +import tools.refinery.viatra.runtime.matchers.psystem.EnumerablePConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration; +import tools.refinery.viatra.runtime.rete.recipes.EqualityFilterRecipe; +import tools.refinery.viatra.runtime.rete.recipes.IndexerBasedAggregatorRecipe; +import tools.refinery.viatra.runtime.rete.recipes.IndexerRecipe; +import tools.refinery.viatra.runtime.rete.recipes.JoinRecipe; +import tools.refinery.viatra.runtime.rete.recipes.Mask; +import tools.refinery.viatra.runtime.rete.recipes.MonotonicityInfo; +import tools.refinery.viatra.runtime.rete.recipes.ProductionRecipe; +import tools.refinery.viatra.runtime.rete.recipes.ProjectionIndexerRecipe; +import tools.refinery.viatra.runtime.rete.recipes.RecipesFactory; +import tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe; +import tools.refinery.viatra.runtime.rete.recipes.SingleColumnAggregatorRecipe; +import tools.refinery.viatra.runtime.rete.recipes.TrimmerRecipe; +import tools.refinery.viatra.runtime.rete.recipes.helper.RecipesHelper; +import tools.refinery.viatra.runtime.rete.traceability.CompiledQuery; +import tools.refinery.viatra.runtime.rete.traceability.CompiledSubPlan; +import tools.refinery.viatra.runtime.rete.traceability.PlanningTrace; +import tools.refinery.viatra.runtime.rete.traceability.RecipeTraceInfo; +import tools.refinery.viatra.runtime.rete.util.ReteHintOptions; + +/** + * @author Bergmann Gabor + * + */ +public class CompilerHelper { + + private CompilerHelper() {/*Utility class constructor*/} + + static final RecipesFactory FACTORY = RecipesFactory.eINSTANCE; + + /** + * Makes sure that all variables in the tuple are different so that it can be used as {@link CompiledSubPlan}. If a + * variable occurs multiple times, equality checks are applied and then the results are trimmed so that duplicates + * are hidden. If no manipulation is necessary, the original trace is returned. + * + *

    + * to be used whenever a constraint introduces new variables. + */ + public static PlanningTrace checkAndTrimEqualVariables(SubPlan plan, final PlanningTrace coreTrace) { + // are variables in the constraint all different? + final List coreVariablesTuple = coreTrace.getVariablesTuple(); + final int constraintArity = coreVariablesTuple.size(); + final int distinctVariables = coreTrace.getPosMapping().size(); + if (constraintArity == distinctVariables) { + // all variables occur exactly once in tuple + return coreTrace; + } else { // apply equality checks and trim + + // find the positions in the tuple for each variable + Map> posMultimap = new HashMap>(); + List trimmedVariablesTuple = new ArrayList(distinctVariables); + int[] trimIndices = new int[distinctVariables]; + for (int i = 0; i < constraintArity; ++i) { + final PVariable variable = coreVariablesTuple.get(i); + SortedSet indexSet = posMultimap.get(variable); + if (indexSet == null) { // first occurrence of variable + indexSet = new TreeSet(); + posMultimap.put(variable, indexSet); + + // this is the first occurrence, set up trimming + trimIndices[trimmedVariablesTuple.size()] = i; + trimmedVariablesTuple.add(variable); + } + indexSet.add(i); + } + + // construct equality checks for each variable occurring multiple times + PlanningTrace lastTrace = coreTrace; + for (Entry> entry : posMultimap.entrySet()) { + if (entry.getValue().size() > 1) { + EqualityFilterRecipe equalityFilterRecipe = FACTORY.createEqualityFilterRecipe(); + equalityFilterRecipe.setParent(lastTrace.getRecipe()); + equalityFilterRecipe.getIndices().addAll(entry.getValue()); + lastTrace = new PlanningTrace(plan, coreVariablesTuple, equalityFilterRecipe, lastTrace); + } + } + + // trim so that each variable occurs only once + TrimmerRecipe trimmerRecipe = FACTORY.createTrimmerRecipe(); + trimmerRecipe.setParent(lastTrace.getRecipe()); + trimmerRecipe.setMask(tools.refinery.viatra.runtime.rete.recipes.helper.RecipesHelper + .mask(constraintArity, trimIndices)); + return new PlanningTrace(plan, trimmedVariablesTuple, trimmerRecipe, lastTrace); + } + } + + /** + * Extracts the variable list representation of the variables tuple. + */ + public static List convertVariablesTuple(EnumerablePConstraint constraint) { + return convertVariablesTuple(constraint.getVariablesTuple()); + } + + /** + * Extracts the variable list representation of the variables tuple. + */ + public static List convertVariablesTuple(Tuple variablesTuple) { + List result = new ArrayList(); + for (Object o : variablesTuple.getElements()) + result.add((PVariable) o); + return result; + } + + /** + * Returns a compiled indexer trace according to a mask + */ + public static RecipeTraceInfo makeIndexerTrace(SubPlan planToCompile, PlanningTrace parentTrace, TupleMask mask) { + final ReteNodeRecipe parentRecipe = parentTrace.getRecipe(); + if (parentRecipe instanceof IndexerBasedAggregatorRecipe + || parentRecipe instanceof SingleColumnAggregatorRecipe) + throw new IllegalArgumentException( + "Cannot take projection indexer of aggregator node at plan " + planToCompile); + IndexerRecipe recipe = RecipesHelper.projectionIndexerRecipe(parentRecipe, toRecipeMask(mask)); + // final List maskedVariables = mask.transform(parentTrace.getVariablesTuple()); + return new PlanningTrace(planToCompile, /* maskedVariables */ parentTrace.getVariablesTuple(), recipe, + parentTrace); + // TODO add specialized indexer trace info? + } + + /** + * Creates a trimmer that keeps selected variables only. + */ + protected static TrimmerRecipe makeTrimmerRecipe(final PlanningTrace compiledParent, + List projectedVariables) { + final Mask projectionMask = makeProjectionMask(compiledParent, projectedVariables); + final TrimmerRecipe trimmerRecipe = ReteRecipeCompiler.FACTORY.createTrimmerRecipe(); + trimmerRecipe.setParent(compiledParent.getRecipe()); + trimmerRecipe.setMask(projectionMask); + return trimmerRecipe; + } + + public static Mask makeProjectionMask(final PlanningTrace compiledParent, Iterable projectedVariables) { + List projectionSourceIndices = new ArrayList(); + for (PVariable pVariable : projectedVariables) { + projectionSourceIndices.add(compiledParent.getPosMapping().get(pVariable)); + } + final Mask projectionMask = RecipesHelper.mask(compiledParent.getRecipe().getArity(), projectionSourceIndices); + return projectionMask; + } + + /** + * @since 1.6 + */ + public static final class PosetTriplet { + public Mask coreMask; + public Mask posetMask; + public IPosetComparator comparator; + } + + /** + * @since 1.6 + */ + public static PosetTriplet computePosetInfo(List variables, PBody body, IQueryMetaContext context) { + Map> typeMap = TypeHelper.inferUnaryTypesFor(variables, body.getConstraints(), + context); + List> keys = new LinkedList>(); + + for (int i = 0; i < variables.size(); i++) { + keys.add(typeMap.get(variables.get(i))); + } + + return computePosetInfo(keys, context); + } + + /** + * @since 1.6 + */ + public static PosetTriplet computePosetInfo(List parameters, IQueryMetaContext context) { + List> keys = new LinkedList>(); + for (int i = 0; i < parameters.size(); i++) { + IInputKey key = parameters.get(i).getDeclaredUnaryType(); + if (key == null) { + keys.add(Collections.emptySet()); + } else { + keys.add(Collections.singleton(parameters.get(i).getDeclaredUnaryType())); + } + } + return computePosetInfo(keys, context); + } + + + + /** + * @since 1.6 + */ + public static PosetTriplet computePosetInfo(Iterable> keys, IQueryMetaContext context) { + PosetTriplet result = new PosetTriplet(); + List coreIndices = new ArrayList(); + List posetIndices = new ArrayList(); + List filtered = new ArrayList(); + boolean posetKey = false; + int index = -1; + + for (Set _keys : keys) { + ++index; + posetKey = false; + + for (IInputKey key : _keys) { + if (key != null && context.isPosetKey(key)) { + posetKey = true; + filtered.add(key); + break; + } + } + + if (posetKey) { + posetIndices.add(index); + } else { + coreIndices.add(index); + } + } + + result.comparator = context.getPosetComparator(filtered); + result.coreMask = RecipesHelper.mask(index + 1, coreIndices); + result.posetMask = RecipesHelper.mask(index + 1, posetIndices); + + return result; + } + + /** + * Creates a recipe for a production node and the corresponding trace. + *

    PRE: in case this is a recursion cutoff point (see {@link RecursionCutoffPoint}) + * and bodyFinalTraces will be filled later, + * the object yielded now by bodyFinalTraces.values() must return up-to-date results later + * @since 2.4 + */ + public static CompiledQuery makeQueryTrace(PQuery query, Map bodyFinalTraces, + Collection bodyFinalRecipes, QueryEvaluationHint hint, IQueryMetaContext context, + boolean deleteAndRederiveEvaluation, TimelyConfiguration timelyEvaluation) { + ProductionRecipe recipe = ReteRecipeCompiler.FACTORY.createProductionRecipe(); + + // temporary solution to support the deprecated option for now + boolean deleteAndRederiveEvaluationDep = deleteAndRederiveEvaluation || ReteHintOptions.deleteRederiveEvaluation.getValueOrDefault(hint); + + recipe.setDeleteRederiveEvaluation(deleteAndRederiveEvaluationDep); + + if (deleteAndRederiveEvaluationDep || (timelyEvaluation != null)) { + PosetTriplet triplet = computePosetInfo(query.getParameters(), context); + if (triplet.comparator != null) { + MonotonicityInfo info = FACTORY.createMonotonicityInfo(); + info.setCoreMask(triplet.coreMask); + info.setPosetMask(triplet.posetMask); + info.setPosetComparator(triplet.comparator); + recipe.setOptionalMonotonicityInfo(info); + } + } + + recipe.setPattern(query); + recipe.setPatternFQN(query.getFullyQualifiedName()); + recipe.setTraceInfo(recipe.getPatternFQN()); + recipe.getParents().addAll(bodyFinalRecipes); + for (int i = 0; i < query.getParameterNames().size(); ++i) { + recipe.getMappedIndices().put(query.getParameterNames().get(i), i); + } + + return new CompiledQuery(recipe, bodyFinalTraces, query); + } + + /** + * Calculated index mappings for a join, based on the common variables of the two parent subplans. + * + * @author Gabor Bergmann + * + */ + public static class JoinHelper { + private TupleMask primaryMask; + private TupleMask secondaryMask; + private TupleMask complementerMask; + private RecipeTraceInfo primaryIndexer; + private RecipeTraceInfo secondaryIndexer; + private JoinRecipe naturalJoinRecipe; + private List naturalJoinVariablesTuple; + + /** + * @pre enforceVariableCoincidences() has been called on both sides. + */ + public JoinHelper(SubPlan planToCompile, PlanningTrace primaryCompiled, PlanningTrace callTrace) { + super(); + + Set primaryVariables = new LinkedHashSet(primaryCompiled.getVariablesTuple()); + Set secondaryVariables = new LinkedHashSet(callTrace.getVariablesTuple()); + int oldNodes = 0; + Set introducingSecondaryIndices = new TreeSet(); + for (PVariable var : secondaryVariables) { + if (primaryVariables.contains(var)) + oldNodes++; + else + introducingSecondaryIndices.add(callTrace.getPosMapping().get(var)); + } + List primaryIndices = new ArrayList(oldNodes); + List secondaryIndices = new ArrayList(oldNodes); + for (PVariable var : secondaryVariables) { + if (primaryVariables.contains(var)) { + primaryIndices.add(primaryCompiled.getPosMapping().get(var)); + secondaryIndices.add(callTrace.getPosMapping().get(var)); + } + } + Collection complementerIndices = introducingSecondaryIndices; + + primaryMask = TupleMask.fromSelectedIndices(primaryCompiled.getVariablesTuple().size(), primaryIndices); + secondaryMask = TupleMask.fromSelectedIndices(callTrace.getVariablesTuple().size(), secondaryIndices); + complementerMask = TupleMask.fromSelectedIndices(callTrace.getVariablesTuple().size(), complementerIndices); + + primaryIndexer = makeIndexerTrace(planToCompile, primaryCompiled, primaryMask); + secondaryIndexer = makeIndexerTrace(planToCompile, callTrace, secondaryMask); + + naturalJoinRecipe = FACTORY.createJoinRecipe(); + naturalJoinRecipe.setLeftParent((ProjectionIndexerRecipe) primaryIndexer.getRecipe()); + naturalJoinRecipe.setRightParent((IndexerRecipe) secondaryIndexer.getRecipe()); + naturalJoinRecipe.setRightParentComplementaryMask(CompilerHelper.toRecipeMask(complementerMask)); + + naturalJoinVariablesTuple = new ArrayList(primaryCompiled.getVariablesTuple()); + for (int complementerIndex : complementerMask.indices) + naturalJoinVariablesTuple.add(callTrace.getVariablesTuple().get(complementerIndex)); + } + + public TupleMask getPrimaryMask() { + return primaryMask; + } + + public TupleMask getSecondaryMask() { + return secondaryMask; + } + + public TupleMask getComplementerMask() { + return complementerMask; + } + + public RecipeTraceInfo getPrimaryIndexer() { + return primaryIndexer; + } + + public RecipeTraceInfo getSecondaryIndexer() { + return secondaryIndexer; + } + + public JoinRecipe getNaturalJoinRecipe() { + return naturalJoinRecipe; + } + + public List getNaturalJoinVariablesTuple() { + return naturalJoinVariablesTuple; + } + + } + + /** + * @since 1.4 + */ + public static Mask toRecipeMask(TupleMask mask) { + return RecipesHelper.mask(mask.sourceWidth, mask.indices); + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/plancompiler/RecursionCutoffPoint.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/plancompiler/RecursionCutoffPoint.java new file mode 100644 index 00000000..7d1e4d3a --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/plancompiler/RecursionCutoffPoint.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.construction.plancompiler; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration; +import tools.refinery.viatra.runtime.rete.recipes.ProductionRecipe; +import tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe; +import tools.refinery.viatra.runtime.rete.traceability.CompiledQuery; +import tools.refinery.viatra.runtime.rete.traceability.RecipeTraceInfo; + +/** + * In a recursive query structure, query composition references can be cut off so that the remaining structure is DAG. + * {@link RecursionCutoffPoint} represents one such cut off query composition. + * When the compilation of the recursive query finishes and the compiled form becomes available, + * the {@link RecursionCutoffPoint} has to be signaled to update parent traces and recipes of the recursive call. + * + * @author Bergmann Gabor + * @noreference This class is not intended to be referenced by clients + * + */ +public class RecursionCutoffPoint { + final Map futureTraceMap; + final CompiledQuery compiledQuery; + final ProductionRecipe recipe; + final QueryEvaluationHint hint; + + public RecursionCutoffPoint(PQuery query, QueryEvaluationHint hint, IQueryMetaContext context, boolean deleteAndRederiveEvaluation, TimelyConfiguration timelyEvaluation) { + super(); + this.hint = hint; + this.futureTraceMap = new HashMap<>(); // IMPORTANT: the identity of futureTraceMap.values() will not change + this.compiledQuery = CompilerHelper.makeQueryTrace(query, futureTraceMap, Collections.emptySet(), hint, context, deleteAndRederiveEvaluation, timelyEvaluation); + this.recipe = (ProductionRecipe)compiledQuery.getRecipe(); + if (!compiledQuery.getParentRecipeTraces().isEmpty()) { + throw new IllegalArgumentException(String.format("Recursion cut-off point of query %s has trace parents: %s", + compiledQuery.getQuery(), + prettyPrintParentRecipeTraces(compiledQuery.getParentRecipeTraces()))); + } + if (!recipe.getParents().isEmpty()) { + throw new IllegalArgumentException(String.format("Recursion cut-off point of query %s has recipe parents: %s", + compiledQuery.getQuery(), + prettyPrintParentRecipeTraces(compiledQuery.getParentRecipeTraces()))); + } + } + + + + private String prettyPrintParentRecipeTraces(List trace) { + return trace.stream().map(Object::toString).collect(Collectors.joining(", ")); + } + + /** + * Signals that compilation of the recursive query has terminated, culminating into the given compiled form. + * The query composition that has been cut off will be connected now. + */ + public void mend(CompiledQuery finalCompiledForm) { + futureTraceMap.putAll(finalCompiledForm.getParentRecipeTracesPerBody()); + recipe.getParents().addAll(((ProductionRecipe)finalCompiledForm.getRecipe()).getParents()); + } + + public CompiledQuery getCompiledQuery() { + return compiledQuery; + } + + public ProductionRecipe getRecipe() { + return recipe; + } + + + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/plancompiler/ReteRecipeCompiler.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/plancompiler/ReteRecipeCompiler.java new file mode 100644 index 00000000..6dd270bd --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/plancompiler/ReteRecipeCompiler.java @@ -0,0 +1,948 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.construction.plancompiler; + +import org.apache.log4j.Logger; +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; +import tools.refinery.viatra.runtime.matchers.backend.CommonQueryHintOptions; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendHintProvider; +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.IPosetComparator; +import tools.refinery.viatra.runtime.matchers.context.IQueryCacheContext; +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.planning.IQueryPlannerStrategy; +import tools.refinery.viatra.runtime.matchers.planning.SubPlan; +import tools.refinery.viatra.runtime.matchers.planning.helpers.BuildHelper; +import tools.refinery.viatra.runtime.matchers.planning.operations.*; +import tools.refinery.viatra.runtime.matchers.psystem.*; +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; +import tools.refinery.viatra.runtime.matchers.psystem.analysis.QueryAnalyzer; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.*; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.*; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PVisibility; +import tools.refinery.viatra.runtime.matchers.psystem.rewriters.*; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType; +import tools.refinery.viatra.runtime.matchers.util.IMultiLookup; +import tools.refinery.viatra.runtime.rete.construction.plancompiler.CompilerHelper.JoinHelper; +import tools.refinery.viatra.runtime.rete.construction.plancompiler.CompilerHelper.PosetTriplet; +import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration; +import tools.refinery.viatra.runtime.rete.recipes.*; +import tools.refinery.viatra.runtime.rete.recipes.helper.RecipesHelper; +import tools.refinery.viatra.runtime.rete.traceability.*; +import tools.refinery.viatra.runtime.rete.util.ReteHintOptions; + +import java.util.*; +import java.util.Map.Entry; +import java.util.function.BiFunction; +import java.util.function.Function; + +/** + * Compiles queries and query plans into Rete recipes, traced by respectively a {@link CompiledQuery} or + * {@link CompiledSubPlan}. + * + * @author Bergmann Gabor + * + */ +public class ReteRecipeCompiler { + + private final IQueryPlannerStrategy plannerStrategy; + private final IQueryMetaContext metaContext; + private final IQueryBackendHintProvider hintProvider; + private final PDisjunctionRewriter normalizer; + private final QueryAnalyzer queryAnalyzer; + private final Logger logger; + + /** + * @since 2.2 + */ + protected final boolean deleteAndRederiveEvaluation; + /** + * @since 2.4 + */ + protected final TimelyConfiguration timelyEvaluation; + + /** + * @since 1.5 + */ + public ReteRecipeCompiler(IQueryPlannerStrategy plannerStrategy, Logger logger, IQueryMetaContext metaContext, + IQueryCacheContext queryCacheContext, IQueryBackendHintProvider hintProvider, QueryAnalyzer queryAnalyzer) { + this(plannerStrategy, logger, metaContext, queryCacheContext, hintProvider, queryAnalyzer, false, null); + } + + /** + * @since 2.4 + */ + public ReteRecipeCompiler(IQueryPlannerStrategy plannerStrategy, Logger logger, IQueryMetaContext metaContext, + IQueryCacheContext queryCacheContext, IQueryBackendHintProvider hintProvider, QueryAnalyzer queryAnalyzer, + boolean deleteAndRederiveEvaluation, TimelyConfiguration timelyEvaluation) { + super(); + this.deleteAndRederiveEvaluation = deleteAndRederiveEvaluation; + this.timelyEvaluation = timelyEvaluation; + this.plannerStrategy = plannerStrategy; + this.logger = logger; + this.metaContext = metaContext; + this.queryAnalyzer = queryAnalyzer; + this.normalizer = new PDisjunctionRewriterCacher(new SurrogateQueryRewriter(), + new PBodyNormalizer(metaContext) { + + @Override + protected boolean shouldExpandWeakenedAlternatives(PQuery query) { + QueryEvaluationHint hint = ReteRecipeCompiler.this.hintProvider.getQueryEvaluationHint(query); + Boolean expandWeakenedAlternativeConstraints = ReteHintOptions.expandWeakenedAlternativeConstraints + .getValueOrDefault(hint); + return expandWeakenedAlternativeConstraints; + } + + }); + this.hintProvider = hintProvider; + } + + static final RecipesFactory FACTORY = RecipesFactory.eINSTANCE; + + // INTERNALLY CACHED + private Map plannerCache = new HashMap(); + private Set planningInProgress = new HashSet(); + + private Map queryCompilerCache = new HashMap(); + private Set compilationInProgress = new HashSet(); + private IMultiLookup recursionCutoffPoints = CollectionsFactory.createMultiLookup(Object.class, MemoryType.SETS, Object.class); + private Map subPlanCompilerCache = new HashMap(); + private Map compilerBackTrace = new HashMap(); + + /** + * Clears internal state + */ + public void reset() { + plannerCache.clear(); + planningInProgress.clear(); + queryCompilerCache.clear(); + subPlanCompilerCache.clear(); + compilerBackTrace.clear(); + } + + /** + * Returns a {@link CompiledQuery} compiled from a query + * @throws ViatraQueryRuntimeException + */ + public CompiledQuery getCompiledForm(PQuery query) { + CompiledQuery compiled = queryCompilerCache.get(query); + if (compiled == null) { + + IRewriterTraceCollector traceCollector = CommonQueryHintOptions.normalizationTraceCollector + .getValueOrDefault(hintProvider.getQueryEvaluationHint(query)); + if (traceCollector != null) { + traceCollector.addTrace(query, query); + } + + boolean reentrant = !compilationInProgress.add(query); + if (reentrant) { // oops, recursion into body in progress + RecursionCutoffPoint cutoffPoint = new RecursionCutoffPoint(query, getHints(query), metaContext, + deleteAndRederiveEvaluation, timelyEvaluation); + recursionCutoffPoints.addPair(query, cutoffPoint); + return cutoffPoint.getCompiledQuery(); + } else { // not reentrant, therefore no recursion, do the compilation + try { + compiled = compileProduction(query); + queryCompilerCache.put(query, compiled); + // backTrace.put(compiled.getRecipe(), plan); + + // if this was a recursive query, mend all points where recursion was cut off + for (RecursionCutoffPoint cutoffPoint : recursionCutoffPoints.lookupOrEmpty(query)) { + cutoffPoint.mend(compiled); + } + } finally { + compilationInProgress.remove(query); + } + } + } + return compiled; + } + + /** + * Returns a {@link CompiledSubPlan} compiled from a query plan + * @throws ViatraQueryRuntimeException + */ + public CompiledSubPlan getCompiledForm(SubPlan plan) { + CompiledSubPlan compiled = subPlanCompilerCache.get(plan); + if (compiled == null) { + compiled = doCompileDispatch(plan); + subPlanCompilerCache.put(plan, compiled); + compilerBackTrace.put(compiled.getRecipe(), plan); + } + return compiled; + } + + /** + * @throws ViatraQueryRuntimeException + */ + public SubPlan getPlan(PBody pBody) { + // if the query is not marked as being compiled, initiate compilation + // (this is useful in case of recursion if getPlan() is the entry point) + PQuery pQuery = pBody.getPattern(); + if (!compilationInProgress.contains(pQuery)) + getCompiledForm(pQuery); + + // Is the plan already cached? + SubPlan plan = plannerCache.get(pBody); + if (plan == null) { + boolean reentrant = !planningInProgress.add(pBody); + if (reentrant) { // oops, recursion into body in progress + throw new IllegalArgumentException( + "Planning-level recursion unsupported: " + pBody.getPattern().getFullyQualifiedName()); + } else { // not reentrant, therefore no recursion, do the planning + try { + plan = plannerStrategy.plan(pBody, logger, metaContext); + plannerCache.put(pBody, plan); + } finally { + planningInProgress.remove(pBody); + } + } + } + return plan; + } + + private CompiledQuery compileProduction(PQuery query) { + Collection bodyPlans = new ArrayList(); + normalizer.setTraceCollector(CommonQueryHintOptions.normalizationTraceCollector + .getValueOrDefault(hintProvider.getQueryEvaluationHint(query))); + for (PBody pBody : normalizer.rewrite(query).getBodies()) { + SubPlan bodyPlan = getPlan(pBody); + bodyPlans.add(bodyPlan); + } + return doCompileProduction(query, bodyPlans); + } + + private CompiledQuery doCompileProduction(PQuery query, Collection bodies) { + // TODO skip production node if there is just one body and no projection needed? + Map bodyFinalTraces = new HashMap(); + Collection bodyFinalRecipes = new HashSet(); + + for (SubPlan bodyFinalPlan : bodies) { + // skip over any projections at the end + bodyFinalPlan = BuildHelper.eliminateTrailingProjections(bodyFinalPlan); + + // TODO checkAndTrimEqualVariables may introduce superfluous trim, + // but whatever (no uniqueness enforcer needed) + + // compile body + final CompiledSubPlan compiledBody = getCompiledForm(bodyFinalPlan); + + // project to parameter list + RecipeTraceInfo finalTrace = projectBodyFinalToParameters(compiledBody, false); + + bodyFinalTraces.put(bodyFinalPlan.getBody(), finalTrace); + bodyFinalRecipes.add(finalTrace.getRecipe()); + } + + CompiledQuery compiled = CompilerHelper.makeQueryTrace(query, bodyFinalTraces, bodyFinalRecipes, + getHints(query), metaContext, deleteAndRederiveEvaluation, timelyEvaluation); + + return compiled; + } + + private CompiledSubPlan doCompileDispatch(SubPlan plan) { + final POperation operation = plan.getOperation(); + if (operation instanceof PEnumerate) { + return doCompileEnumerate(((PEnumerate) operation).getEnumerablePConstraint(), plan); + } else if (operation instanceof PApply) { + final PConstraint pConstraint = ((PApply) operation).getPConstraint(); + if (pConstraint instanceof EnumerablePConstraint) { + CompiledSubPlan primaryParent = getCompiledForm(plan.getParentPlans().get(0)); + PlanningTrace secondaryParent = doEnumerateDispatch(plan, (EnumerablePConstraint) pConstraint); + return compileToNaturalJoin(plan, primaryParent, secondaryParent); + } else if (pConstraint instanceof DeferredPConstraint) { + return doDeferredDispatch((DeferredPConstraint) pConstraint, plan); + } else { + throw new IllegalArgumentException("Unsupported PConstraint in query plan: " + plan.toShortString()); + } + } else if (operation instanceof PJoin) { + return doCompileJoin((PJoin) operation, plan); + } else if (operation instanceof PProject) { + return doCompileProject((PProject) operation, plan); + } else if (operation instanceof PStart) { + return doCompileStart((PStart) operation, plan); + } else { + throw new IllegalArgumentException("Unsupported POperation in query plan: " + plan.toShortString()); + } + } + + private CompiledSubPlan doDeferredDispatch(DeferredPConstraint constraint, SubPlan plan) { + final SubPlan parentPlan = plan.getParentPlans().get(0); + final CompiledSubPlan parentCompiled = getCompiledForm(parentPlan); + if (constraint instanceof Equality) { + return compileDeferred((Equality) constraint, plan, parentPlan, parentCompiled); + } else if (constraint instanceof ExportedParameter) { + return compileDeferred((ExportedParameter) constraint, plan, parentPlan, parentCompiled); + } else if (constraint instanceof Inequality) { + return compileDeferred((Inequality) constraint, plan, parentPlan, parentCompiled); + } else if (constraint instanceof NegativePatternCall) { + return compileDeferred((NegativePatternCall) constraint, plan, parentPlan, parentCompiled); + } else if (constraint instanceof PatternMatchCounter) { + return compileDeferred((PatternMatchCounter) constraint, plan, parentPlan, parentCompiled); + } else if (constraint instanceof AggregatorConstraint) { + return compileDeferred((AggregatorConstraint) constraint, plan, parentPlan, parentCompiled); + } else if (constraint instanceof ExpressionEvaluation) { + return compileDeferred((ExpressionEvaluation) constraint, plan, parentPlan, parentCompiled); + } else if (constraint instanceof TypeFilterConstraint) { + return compileDeferred((TypeFilterConstraint) constraint, plan, parentPlan, parentCompiled); + } + throw new UnsupportedOperationException("Unknown deferred constraint " + constraint); + } + + private CompiledSubPlan compileDeferred(Equality constraint, SubPlan plan, SubPlan parentPlan, + CompiledSubPlan parentCompiled) { + if (constraint.isMoot()) + return parentCompiled.cloneFor(plan); + + Integer index1 = parentCompiled.getPosMapping().get(constraint.getWho()); + Integer index2 = parentCompiled.getPosMapping().get(constraint.getWithWhom()); + + if (index1 != null && index2 != null && index1.intValue() != index2.intValue()) { + Integer indexLower = Math.min(index1, index2); + Integer indexHigher = Math.max(index1, index2); + + EqualityFilterRecipe equalityFilterRecipe = FACTORY.createEqualityFilterRecipe(); + equalityFilterRecipe.setParent(parentCompiled.getRecipe()); + equalityFilterRecipe.getIndices().add(indexLower); + equalityFilterRecipe.getIndices().add(indexHigher); + + return new CompiledSubPlan(plan, parentCompiled.getVariablesTuple(), equalityFilterRecipe, parentCompiled); + } else { + throw new IllegalArgumentException(String.format("Unable to interpret %s after compiled parent %s", + plan.toShortString(), parentCompiled.toString())); + } + } + + /** + * Precondition: constantTrace must map to a ConstantRecipe, and all of its variables must be contained in + * toFilterTrace. + */ + private CompiledSubPlan compileConstantFiltering(SubPlan plan, PlanningTrace toFilterTrace, + ConstantRecipe constantRecipe, List filteredVariables) { + PlanningTrace resultTrace = toFilterTrace; + + int constantVariablesSize = filteredVariables.size(); + for (int i = 0; i < constantVariablesSize; ++i) { + Object constantValue = constantRecipe.getConstantValues().get(i); + PVariable filteredVariable = filteredVariables.get(i); + int filteredColumn = resultTrace.getVariablesTuple().indexOf(filteredVariable); + + DiscriminatorDispatcherRecipe dispatcherRecipe = FACTORY.createDiscriminatorDispatcherRecipe(); + dispatcherRecipe.setDiscriminationColumnIndex(filteredColumn); + dispatcherRecipe.setParent(resultTrace.getRecipe()); + + PlanningTrace dispatcherTrace = new PlanningTrace(plan, resultTrace.getVariablesTuple(), dispatcherRecipe, + resultTrace); + + DiscriminatorBucketRecipe bucketRecipe = FACTORY.createDiscriminatorBucketRecipe(); + bucketRecipe.setBucketKey(constantValue); + bucketRecipe.setParent(dispatcherRecipe); + + PlanningTrace bucketTrace = new PlanningTrace(plan, dispatcherTrace.getVariablesTuple(), bucketRecipe, + dispatcherTrace); + + resultTrace = bucketTrace; + } + + return resultTrace.cloneFor(plan); + } + + private CompiledSubPlan compileDeferred(ExportedParameter constraint, SubPlan plan, SubPlan parentPlan, + CompiledSubPlan parentCompiled) { + return parentCompiled.cloneFor(plan); + } + + private CompiledSubPlan compileDeferred(Inequality constraint, SubPlan plan, SubPlan parentPlan, + CompiledSubPlan parentCompiled) { + if (constraint.isEliminable()) + return parentCompiled.cloneFor(plan); + + Integer index1 = parentCompiled.getPosMapping().get(constraint.getWho()); + Integer index2 = parentCompiled.getPosMapping().get(constraint.getWithWhom()); + + if (index1 != null && index2 != null && index1.intValue() != index2.intValue()) { + Integer indexLower = Math.min(index1, index2); + Integer indexHigher = Math.max(index1, index2); + + InequalityFilterRecipe inequalityFilterRecipe = FACTORY.createInequalityFilterRecipe(); + inequalityFilterRecipe.setParent(parentCompiled.getRecipe()); + inequalityFilterRecipe.setSubject(indexLower); + inequalityFilterRecipe.getInequals().add(indexHigher); + + return new CompiledSubPlan(plan, parentCompiled.getVariablesTuple(), inequalityFilterRecipe, + parentCompiled); + } else { + throw new IllegalArgumentException(String.format("Unable to interpret %s after compiled parent %s", + plan.toShortString(), parentCompiled.toString())); + } + } + + private CompiledSubPlan compileDeferred(TypeFilterConstraint constraint, SubPlan plan, SubPlan parentPlan, + CompiledSubPlan parentCompiled) { + final IInputKey inputKey = constraint.getInputKey(); + if (!metaContext.isStateless(inputKey)) + throw new UnsupportedOperationException( + "Non-enumerable input keys are currently supported in Rete only if they are stateless, unlike " + + inputKey); + + final Tuple constraintVariables = constraint.getVariablesTuple(); + final List parentVariables = parentCompiled.getVariablesTuple(); + + Mask mask; // select elements of the tuple to check against extensional relation + if (Tuples.flatTupleOf(parentVariables.toArray()).equals(constraintVariables)) { + mask = null; // lucky case, parent signature equals that of input key + } else { + List variables = new ArrayList(); + for (Object variable : constraintVariables.getElements()) { + variables.add((PVariable) variable); + } + mask = CompilerHelper.makeProjectionMask(parentCompiled, variables); + } + InputFilterRecipe inputFilterRecipe = RecipesHelper.inputFilterRecipe(parentCompiled.getRecipe(), inputKey, + inputKey.getStringID(), mask); + return new CompiledSubPlan(plan, parentVariables, inputFilterRecipe, parentCompiled); + } + + private CompiledSubPlan compileDeferred(NegativePatternCall constraint, SubPlan plan, SubPlan parentPlan, + CompiledSubPlan parentCompiled) { + final PlanningTrace callTrace = referQuery(constraint.getReferredQuery(), plan, + constraint.getActualParametersTuple()); + + JoinHelper joinHelper = new JoinHelper(plan, parentCompiled, callTrace); + final RecipeTraceInfo primaryIndexer = joinHelper.getPrimaryIndexer(); + final RecipeTraceInfo secondaryIndexer = joinHelper.getSecondaryIndexer(); + + AntiJoinRecipe antiJoinRecipe = FACTORY.createAntiJoinRecipe(); + antiJoinRecipe.setLeftParent((ProjectionIndexerRecipe) primaryIndexer.getRecipe()); + antiJoinRecipe.setRightParent((IndexerRecipe) secondaryIndexer.getRecipe()); + + return new CompiledSubPlan(plan, parentCompiled.getVariablesTuple(), antiJoinRecipe, primaryIndexer, + secondaryIndexer); + } + + private CompiledSubPlan compileDeferred(PatternMatchCounter constraint, SubPlan plan, SubPlan parentPlan, + CompiledSubPlan parentCompiled) { + final PlanningTrace callTrace = referQuery(constraint.getReferredQuery(), plan, + constraint.getActualParametersTuple()); + + // hack: use some mask computations (+ the indexers) from a fake natural join against the called query + JoinHelper fakeJoinHelper = new JoinHelper(plan, parentCompiled, callTrace); + final RecipeTraceInfo primaryIndexer = fakeJoinHelper.getPrimaryIndexer(); + final RecipeTraceInfo callProjectionIndexer = fakeJoinHelper.getSecondaryIndexer(); + + final List sideVariablesTuple = new ArrayList( + fakeJoinHelper.getSecondaryMask().transform(callTrace.getVariablesTuple())); + /* if (!booleanCheck) */ sideVariablesTuple.add(constraint.getResultVariable()); + + CountAggregatorRecipe aggregatorRecipe = FACTORY.createCountAggregatorRecipe(); + aggregatorRecipe.setParent((ProjectionIndexerRecipe) callProjectionIndexer.getRecipe()); + PlanningTrace aggregatorTrace = new PlanningTrace(plan, sideVariablesTuple, aggregatorRecipe, + callProjectionIndexer); + + IndexerRecipe aggregatorIndexerRecipe = FACTORY.createAggregatorIndexerRecipe(); + aggregatorIndexerRecipe.setParent(aggregatorRecipe); + // aggregatorIndexerRecipe.setMask(RecipesHelper.mask( + // sideVariablesTuple.size(), + // //use same indices as in the projection indexer + // // EVEN if result variable already visible in left parent + // fakeJoinHelper.getSecondaryMask().indices + // )); + + int aggregatorWidth = sideVariablesTuple.size(); + int aggregateResultIndex = aggregatorWidth - 1; + + aggregatorIndexerRecipe.setMask(CompilerHelper.toRecipeMask(TupleMask.omit( + // aggregate according all but the last index + aggregateResultIndex, aggregatorWidth))); + PlanningTrace aggregatorIndexerTrace = new PlanningTrace(plan, sideVariablesTuple, aggregatorIndexerRecipe, + aggregatorTrace); + + JoinRecipe naturalJoinRecipe = FACTORY.createJoinRecipe(); + naturalJoinRecipe.setLeftParent((ProjectionIndexerRecipe) primaryIndexer.getRecipe()); + naturalJoinRecipe.setRightParent(aggregatorIndexerRecipe); + naturalJoinRecipe.setRightParentComplementaryMask(RecipesHelper.mask(aggregatorWidth, + // extend with last element only - the computation value + aggregateResultIndex)); + + // what if the new variable already has a value? + // even if already known, we add the new result variable, so that it can be filtered at the end + // boolean alreadyKnown = parentPlan.getVisibleVariables().contains(constraint.getResultVariable()); + + final List aggregatedVariablesTuple = new ArrayList(parentCompiled.getVariablesTuple()); + aggregatedVariablesTuple.add(constraint.getResultVariable()); + + PlanningTrace joinTrace = new PlanningTrace(plan, aggregatedVariablesTuple, naturalJoinRecipe, primaryIndexer, + aggregatorIndexerTrace); + + return CompilerHelper.checkAndTrimEqualVariables(plan, joinTrace).cloneFor(plan); + // if (!alreadyKnown) { + // return joinTrace.cloneFor(plan); + // } else { + // //final Integer equalsWithIndex = parentCompiled.getPosMapping().get(parentCompiled.getVariablesTuple()); + // } + } + + private CompiledSubPlan compileDeferred(AggregatorConstraint constraint, SubPlan plan, SubPlan parentPlan, + CompiledSubPlan parentCompiled) { + final PlanningTrace callTrace = referQuery(constraint.getReferredQuery(), plan, + constraint.getActualParametersTuple()); + + // hack: use some mask computations (+ the indexers) from a fake natural join against the called query + JoinHelper fakeJoinHelper = new JoinHelper(plan, parentCompiled, callTrace); + final RecipeTraceInfo primaryIndexer = fakeJoinHelper.getPrimaryIndexer(); + TupleMask callGroupMask = fakeJoinHelper.getSecondaryMask(); + + final List sideVariablesTuple = new ArrayList( + callGroupMask.transform(callTrace.getVariablesTuple())); + /* if (!booleanCheck) */ sideVariablesTuple.add(constraint.getResultVariable()); + + IMultisetAggregationOperator operator = constraint.getAggregator().getOperator(); + + SingleColumnAggregatorRecipe columnAggregatorRecipe = FACTORY.createSingleColumnAggregatorRecipe(); + columnAggregatorRecipe.setParent(callTrace.getRecipe()); + columnAggregatorRecipe.setMultisetAggregationOperator(operator); + + int columnIndex = constraint.getAggregatedColumn(); + IPosetComparator posetComparator = null; + Mask groupMask = CompilerHelper.toRecipeMask(callGroupMask); + + // temporary solution to support the deprecated option for now + final boolean deleteAndRederiveEvaluationDep = this.deleteAndRederiveEvaluation || ReteHintOptions.deleteRederiveEvaluation.getValueOrDefault(getHints(plan)); + + columnAggregatorRecipe.setDeleteRederiveEvaluation(deleteAndRederiveEvaluationDep); + if (deleteAndRederiveEvaluationDep || (this.timelyEvaluation != null)) { + List parameters = constraint.getReferredQuery().getParameters(); + IInputKey key = parameters.get(columnIndex).getDeclaredUnaryType(); + if (key != null && metaContext.isPosetKey(key)) { + posetComparator = metaContext.getPosetComparator(Collections.singleton(key)); + } + } + + if (posetComparator == null) { + columnAggregatorRecipe.setGroupByMask(groupMask); + columnAggregatorRecipe.setAggregableIndex(columnIndex); + } else { + MonotonicityInfo monotonicityInfo = FACTORY.createMonotonicityInfo(); + monotonicityInfo.setCoreMask(groupMask); + monotonicityInfo.setPosetMask(CompilerHelper.toRecipeMask( + TupleMask.selectSingle(columnIndex, constraint.getActualParametersTuple().getSize()))); + monotonicityInfo.setPosetComparator(posetComparator); + columnAggregatorRecipe.setOptionalMonotonicityInfo(monotonicityInfo); + } + + ReteNodeRecipe aggregatorRecipe = columnAggregatorRecipe; + PlanningTrace aggregatorTrace = new PlanningTrace(plan, sideVariablesTuple, aggregatorRecipe, callTrace); + + IndexerRecipe aggregatorIndexerRecipe = FACTORY.createAggregatorIndexerRecipe(); + aggregatorIndexerRecipe.setParent(aggregatorRecipe); + + int aggregatorWidth = sideVariablesTuple.size(); + int aggregateResultIndex = aggregatorWidth - 1; + + aggregatorIndexerRecipe.setMask(CompilerHelper.toRecipeMask(TupleMask.omit( + // aggregate according all but the last index + aggregateResultIndex, aggregatorWidth))); + PlanningTrace aggregatorIndexerTrace = new PlanningTrace(plan, sideVariablesTuple, aggregatorIndexerRecipe, + aggregatorTrace); + + JoinRecipe naturalJoinRecipe = FACTORY.createJoinRecipe(); + naturalJoinRecipe.setLeftParent((ProjectionIndexerRecipe) primaryIndexer.getRecipe()); + naturalJoinRecipe.setRightParent(aggregatorIndexerRecipe); + naturalJoinRecipe.setRightParentComplementaryMask(RecipesHelper.mask(aggregatorWidth, + // extend with last element only - the computation value + aggregateResultIndex)); + + // what if the new variable already has a value? + // even if already known, we add the new result variable, so that it can be filtered at the end + // boolean alreadyKnown = parentPlan.getVisibleVariables().contains(constraint.getResultVariable()); + + final List finalVariablesTuple = new ArrayList(parentCompiled.getVariablesTuple()); + finalVariablesTuple.add(constraint.getResultVariable()); + + PlanningTrace joinTrace = new PlanningTrace(plan, finalVariablesTuple, naturalJoinRecipe, primaryIndexer, + aggregatorIndexerTrace); + + return CompilerHelper.checkAndTrimEqualVariables(plan, joinTrace).cloneFor(plan); + // if (!alreadyKnown) { + // return joinTrace.cloneFor(plan); + // } else { + // //final Integer equalsWithIndex = parentCompiled.getPosMapping().get(parentCompiled.getVariablesTuple()); + // } + } + + private CompiledSubPlan compileDeferred(ExpressionEvaluation constraint, SubPlan plan, SubPlan parentPlan, + CompiledSubPlan parentCompiled) { + Map tupleNameMap = new HashMap(); + for (String name : constraint.getEvaluator().getInputParameterNames()) { + Map index = parentCompiled.getPosMapping(); + PVariable variable = constraint.getPSystem().getVariableByNameChecked(name); + Integer position = index.get(variable); + tupleNameMap.put(name, position); + } + + final PVariable outputVariable = constraint.getOutputVariable(); + final boolean booleanCheck = outputVariable == null; + + // TODO determine whether expression is costly + boolean cacheOutput = ReteHintOptions.cacheOutputOfEvaluatorsByDefault.getValueOrDefault(getHints(plan)); + // for (PAnnotation pAnnotation : + // plan.getBody().getPattern().getAnnotationsByName(EXPRESSION_EVALUATION_ANNOTATION"")) { + // for (Object value : pAnnotation.getAllValues("expensive")) { + // if (value instanceof Boolean) + // cacheOutput = (boolean) value; + // } + // } + + ExpressionEnforcerRecipe enforcerRecipe = booleanCheck ? FACTORY.createCheckRecipe() + : FACTORY.createEvalRecipe(); + enforcerRecipe.setParent(parentCompiled.getRecipe()); + enforcerRecipe.setExpression(RecipesHelper.expressionDefinition(constraint.getEvaluator())); + enforcerRecipe.setCacheOutput(cacheOutput); + if (enforcerRecipe instanceof EvalRecipe) { + ((EvalRecipe) enforcerRecipe).setUnwinding(constraint.isUnwinding()); + } + for (Entry entry : tupleNameMap.entrySet()) { + enforcerRecipe.getMappedIndices().put(entry.getKey(), entry.getValue()); + } + + final List enforcerVariablesTuple = new ArrayList(parentCompiled.getVariablesTuple()); + if (!booleanCheck) + enforcerVariablesTuple.add(outputVariable); + PlanningTrace enforcerTrace = new PlanningTrace(plan, enforcerVariablesTuple, enforcerRecipe, parentCompiled); + + return CompilerHelper.checkAndTrimEqualVariables(plan, enforcerTrace).cloneFor(plan); + } + + private CompiledSubPlan doCompileJoin(PJoin operation, SubPlan plan) { + final List compiledParents = getCompiledFormOfParents(plan); + final CompiledSubPlan leftCompiled = compiledParents.get(0); + final CompiledSubPlan rightCompiled = compiledParents.get(1); + + return compileToNaturalJoin(plan, leftCompiled, rightCompiled); + } + + private CompiledSubPlan compileToNaturalJoin(SubPlan plan, final PlanningTrace leftCompiled, + final PlanningTrace rightCompiled) { + // CHECK IF SPECIAL CASE + + // Is constant filtering applicable? + if (ReteHintOptions.useDiscriminatorDispatchersForConstantFiltering.getValueOrDefault(getHints(plan))) { + if (leftCompiled.getRecipe() instanceof ConstantRecipe + && rightCompiled.getVariablesTuple().containsAll(leftCompiled.getVariablesTuple())) { + return compileConstantFiltering(plan, rightCompiled, (ConstantRecipe) leftCompiled.getRecipe(), + leftCompiled.getVariablesTuple()); + } + if (rightCompiled.getRecipe() instanceof ConstantRecipe + && leftCompiled.getVariablesTuple().containsAll(rightCompiled.getVariablesTuple())) { + return compileConstantFiltering(plan, leftCompiled, (ConstantRecipe) rightCompiled.getRecipe(), + rightCompiled.getVariablesTuple()); + } + } + + // ELSE: ACTUAL JOIN + JoinHelper joinHelper = new JoinHelper(plan, leftCompiled, rightCompiled); + return new CompiledSubPlan(plan, joinHelper.getNaturalJoinVariablesTuple(), joinHelper.getNaturalJoinRecipe(), + joinHelper.getPrimaryIndexer(), joinHelper.getSecondaryIndexer()); + } + + private CompiledSubPlan doCompileProject(PProject operation, SubPlan plan) { + final List compiledParents = getCompiledFormOfParents(plan); + final CompiledSubPlan compiledParent = compiledParents.get(0); + + List projectedVariables = new ArrayList(operation.getToVariables()); + // Determinizing projection: try to keep original order (hopefully facilitates node reuse) + Map parentPosMapping = compiledParent.getPosMapping(); + Collections.sort(projectedVariables, Comparator.comparing(parentPosMapping::get)); + + return doProjectPlan(compiledParent, projectedVariables, true, + parentTrace -> parentTrace.cloneFor(plan), + (recipe, parentTrace) -> new PlanningTrace(plan, projectedVariables, recipe, parentTrace), + (recipe, parentTrace) -> new CompiledSubPlan(plan, projectedVariables, recipe, parentTrace) + ); + } + + /** + * Projects a subplan onto the specified variable tuple + * @param compiledParentPlan the compiled form of the subplan + * @param targetVariables list of variables to project to + * @param enforceUniqueness whether distinctness shall be enforced after the projection. + * Specify false only if directly connecting to a production node. + * @param reinterpretTraceFactory constructs a reinterpreted trace that simply relabels the compiled parent plan, in case it is sufficient + * @param intermediateTraceFactory constructs a recipe trace for an intermediate node, given the recipe of the node and its parent trace + * @param finalTraceFactory constructs a recipe trace for the final resulting node, given the recipe of the node and its parent trace + * @since 2.1 + */ + ResultTrace doProjectPlan( + final CompiledSubPlan compiledParentPlan, + final List targetVariables, + boolean enforceUniqueness, + Function reinterpretTraceFactory, + BiFunction intermediateTraceFactory, + BiFunction finalTraceFactory) + { + if (targetVariables.equals(compiledParentPlan.getVariablesTuple())) // no projection needed + return reinterpretTraceFactory.apply(compiledParentPlan); + + // otherwise, we need at least a trimmer + TrimmerRecipe trimmerRecipe = CompilerHelper.makeTrimmerRecipe(compiledParentPlan, targetVariables); + + // do we need to eliminate duplicates? + SubPlan parentPlan = compiledParentPlan.getSubPlan(); + if (!enforceUniqueness || BuildHelper.areAllVariablesDetermined( + parentPlan, + targetVariables, + queryAnalyzer, + true)) + { + // if uniqueness enforcess is unwanted or unneeeded, skip it + return finalTraceFactory.apply(trimmerRecipe, compiledParentPlan); + } else { + // add a uniqueness enforcer + UniquenessEnforcerRecipe recipe = FACTORY.createUniquenessEnforcerRecipe(); + recipe.getParents().add(trimmerRecipe); + + // temporary solution to support the deprecated option for now + final boolean deleteAndRederiveEvaluationDep = this.deleteAndRederiveEvaluation || ReteHintOptions.deleteRederiveEvaluation.getValueOrDefault(getHints(parentPlan)); + + recipe.setDeleteRederiveEvaluation(deleteAndRederiveEvaluationDep); + if (deleteAndRederiveEvaluationDep || (this.timelyEvaluation != null)) { + PosetTriplet triplet = CompilerHelper.computePosetInfo(targetVariables, parentPlan.getBody(), metaContext); + + if (triplet.comparator != null) { + MonotonicityInfo info = FACTORY.createMonotonicityInfo(); + info.setCoreMask(triplet.coreMask); + info.setPosetMask(triplet.posetMask); + info.setPosetComparator(triplet.comparator); + recipe.setOptionalMonotonicityInfo(info); + } + } + + RecipeTraceInfo trimmerTrace = intermediateTraceFactory.apply(trimmerRecipe, compiledParentPlan); + return finalTraceFactory.apply(recipe, trimmerTrace); + } + } + + /** + * Projects the final compiled form of a PBody onto the parameter tuple + * @param compiledBody the compiled form of the body, with all constraints enforced, not yet projected to query parameters + * @param enforceUniqueness whether distinctness shall be enforced after the projection. + * Specify false only if directly connecting to a production node. + * @since 2.1 + */ + RecipeTraceInfo projectBodyFinalToParameters( + final CompiledSubPlan compiledBody, + boolean enforceUniqueness) + { + final PBody body = compiledBody.getSubPlan().getBody(); + final List parameterList = body.getSymbolicParameterVariables(); + + return doProjectPlan(compiledBody, parameterList, enforceUniqueness, + parentTrace -> parentTrace, + (recipe, parentTrace) -> new ParameterProjectionTrace(body, recipe, parentTrace), + (recipe, parentTrace) -> new ParameterProjectionTrace(body, recipe, parentTrace) + ); + } + + private CompiledSubPlan doCompileStart(PStart operation, SubPlan plan) { + if (!operation.getAPrioriVariables().isEmpty()) { + throw new IllegalArgumentException("Input variables unsupported by Rete: " + plan.toShortString()); + } + final ConstantRecipe recipe = FACTORY.createConstantRecipe(); + recipe.getConstantValues().clear(); + + return new CompiledSubPlan(plan, new ArrayList(), recipe); + } + + private CompiledSubPlan doCompileEnumerate(EnumerablePConstraint constraint, SubPlan plan) { + final PlanningTrace trimmedTrace = doEnumerateAndDeduplicate(constraint, plan); + + return trimmedTrace.cloneFor(plan); + } + + private PlanningTrace doEnumerateAndDeduplicate(EnumerablePConstraint constraint, SubPlan plan) { + final PlanningTrace coreTrace = doEnumerateDispatch(plan, constraint); + final PlanningTrace trimmedTrace = CompilerHelper.checkAndTrimEqualVariables(plan, coreTrace); + return trimmedTrace; + } + + private PlanningTrace doEnumerateDispatch(SubPlan plan, EnumerablePConstraint constraint) { + if (constraint instanceof RelationEvaluation) { + return compileEnumerable(plan, (RelationEvaluation) constraint); + } else if (constraint instanceof BinaryTransitiveClosure) { + return compileEnumerable(plan, (BinaryTransitiveClosure) constraint); + } else if (constraint instanceof BinaryReflexiveTransitiveClosure) { + return compileEnumerable(plan, (BinaryReflexiveTransitiveClosure) constraint); + } else if (constraint instanceof RepresentativeElectionConstraint) { + return compileEnumerable(plan, (RepresentativeElectionConstraint) constraint); + } else if (constraint instanceof ConstantValue) { + return compileEnumerable(plan, (ConstantValue) constraint); + } else if (constraint instanceof PositivePatternCall) { + return compileEnumerable(plan, (PositivePatternCall) constraint); + } else if (constraint instanceof TypeConstraint) { + return compileEnumerable(plan, (TypeConstraint) constraint); + } + throw new UnsupportedOperationException("Unknown enumerable constraint " + constraint); + } + + private PlanningTrace compileEnumerable(SubPlan plan, BinaryReflexiveTransitiveClosure constraint) { + // TODO the implementation would perform better if an inequality check would be used after tcRecipe and + // uniqueness enforcer be replaced by a transparent node with multiple parents, but such a node is not available + // in recipe metamodel in VIATRA 2.0 + + // Find called query + final PQuery referredQuery = constraint.getSupplierKey(); + final PlanningTrace callTrace = referQuery(referredQuery, plan, constraint.getVariablesTuple()); + + // Calculate irreflexive transitive closure + final TransitiveClosureRecipe tcRecipe = FACTORY.createTransitiveClosureRecipe(); + tcRecipe.setParent(callTrace.getRecipe()); + final PlanningTrace tcTrace = new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), tcRecipe, callTrace); + + // Enumerate universe type + final IInputKey inputKey = constraint.getUniverseType(); + final InputRecipe universeTypeRecipe = RecipesHelper.inputRecipe(inputKey, inputKey.getStringID(), inputKey.getArity()); + final PlanningTrace universeTypeTrace = new PlanningTrace(plan, CompilerHelper.convertVariablesTuple( + Tuples.staticArityFlatTupleOf(constraint.getVariablesTuple().get(0))), universeTypeRecipe); + + // Calculate reflexive access by duplicating universe type column + final TrimmerRecipe reflexiveRecipe = FACTORY.createTrimmerRecipe(); + reflexiveRecipe.setMask(RecipesHelper.mask(1, 0, 0)); + reflexiveRecipe.setParent(universeTypeRecipe); + final PlanningTrace reflexiveTrace = new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), reflexiveRecipe, universeTypeTrace); + + // Finally, reduce duplicates after a join + final UniquenessEnforcerRecipe brtcRecipe = FACTORY.createUniquenessEnforcerRecipe(); + brtcRecipe.getParents().add(tcRecipe); + brtcRecipe.getParents().add(reflexiveRecipe); + + return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), brtcRecipe, reflexiveTrace, tcTrace); + } + + private PlanningTrace compileEnumerable(SubPlan plan, RepresentativeElectionConstraint constraint) { + var referredQuery = constraint.getSupplierKey(); + var callTrace = referQuery(referredQuery, plan, constraint.getVariablesTuple()); + var recipe = FACTORY.createRepresentativeElectionRecipe(); + recipe.setParent(callTrace.getRecipe()); + recipe.setConnectivity(constraint.getConnectivity()); + return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), recipe, callTrace); + } + + private PlanningTrace compileEnumerable(SubPlan plan, BinaryTransitiveClosure constraint) { + final PQuery referredQuery = constraint.getSupplierKey(); + final PlanningTrace callTrace = referQuery(referredQuery, plan, constraint.getVariablesTuple()); + + final TransitiveClosureRecipe recipe = FACTORY.createTransitiveClosureRecipe(); + recipe.setParent(callTrace.getRecipe()); + + return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), recipe, callTrace); + } + + private PlanningTrace compileEnumerable(SubPlan plan, RelationEvaluation constraint) { + final List parentRecipes = new ArrayList(); + final List parentTraceInfos = new ArrayList(); + for (final PQuery inputQuery : constraint.getReferredQueries()) { + final CompiledQuery compiledQuery = getCompiledForm(inputQuery); + parentRecipes.add(compiledQuery.getRecipe()); + parentTraceInfos.add(compiledQuery); + } + final RelationEvaluationRecipe recipe = FACTORY.createRelationEvaluationRecipe(); + recipe.getParents().addAll(parentRecipes); + recipe.setEvaluator(RecipesHelper.expressionDefinition(constraint.getEvaluator())); + return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), recipe, parentTraceInfos); + } + + private PlanningTrace compileEnumerable(SubPlan plan, PositivePatternCall constraint) { + final PQuery referredQuery = constraint.getReferredQuery(); + return referQuery(referredQuery, plan, constraint.getVariablesTuple()); + } + + private PlanningTrace compileEnumerable(SubPlan plan, TypeConstraint constraint) { + final IInputKey inputKey = constraint.getSupplierKey(); + final InputRecipe recipe = RecipesHelper.inputRecipe(inputKey, inputKey.getStringID(), inputKey.getArity()); + return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), recipe); + } + + private PlanningTrace compileEnumerable(SubPlan plan, ConstantValue constraint) { + final ConstantRecipe recipe = FACTORY.createConstantRecipe(); + recipe.getConstantValues().add(constraint.getSupplierKey()); + return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(constraint), recipe); + } + + // TODO handle recursion + private PlanningTrace referQuery(PQuery query, SubPlan plan, Tuple actualParametersTuple) { + RecipeTraceInfo referredQueryTrace = originalTraceOfReferredQuery(query); + return new PlanningTrace(plan, CompilerHelper.convertVariablesTuple(actualParametersTuple), + referredQueryTrace.getRecipe(), referredQueryTrace.getParentRecipeTracesForCloning()); + } + + private RecipeTraceInfo originalTraceOfReferredQuery(PQuery query) { + // eliminate superfluous production node? + if (PVisibility.EMBEDDED == query.getVisibility()) { // currently inline patterns only + Set rewrittenBodies = normalizer.rewrite(query).getBodies(); + if (1 == rewrittenBodies.size()) { // non-disjunctive + // TODO in the future, check if non-recursive - (not currently permitted) + + PBody pBody = rewrittenBodies.iterator().next(); + SubPlan bodyFinalPlan = getPlan(pBody); + + // skip over any projections at the end + bodyFinalPlan = BuildHelper.eliminateTrailingProjections(bodyFinalPlan); + + // TODO checkAndTrimEqualVariables may introduce superfluous trim, + // but whatever (no uniqueness enforcer needed) + + // compile body + final CompiledSubPlan compiledBody = getCompiledForm(bodyFinalPlan); + + // project to parameter list, add uniqueness enforcer if necessary + return projectBodyFinalToParameters(compiledBody, true /* ensure uniqueness, as no production node is used */); + } + } + + // otherwise, regular reference to recipe realizing the query + return getCompiledForm(query); + } + + protected List getCompiledFormOfParents(SubPlan plan) { + List results = new ArrayList(); + for (SubPlan parentPlan : plan.getParentPlans()) { + results.add(getCompiledForm(parentPlan)); + } + return results; + } + + /** + * Returns an unmodifiable view of currently cached compiled queries. + */ + public Map getCachedCompiledQueries() { + return Collections.unmodifiableMap(queryCompilerCache); + } + + /** + * Returns an unmodifiable view of currently cached query plans. + */ + public Map getCachedQueryPlans() { + return Collections.unmodifiableMap(plannerCache); + } + + private QueryEvaluationHint getHints(SubPlan plan) { + return getHints(plan.getBody().getPattern()); + } + + private QueryEvaluationHint getHints(PQuery pattern) { + return hintProvider.getQueryEvaluationHint(pattern); + } +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/quasitree/JoinCandidate.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/quasitree/JoinCandidate.java new file mode 100644 index 00000000..45350099 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/quasitree/JoinCandidate.java @@ -0,0 +1,162 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.construction.quasitree; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import tools.refinery.viatra.runtime.matchers.planning.SubPlan; +import tools.refinery.viatra.runtime.matchers.planning.SubPlanFactory; +import tools.refinery.viatra.runtime.matchers.planning.helpers.FunctionalDependencyHelper; +import tools.refinery.viatra.runtime.matchers.planning.operations.PJoin; +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.psystem.analysis.QueryAnalyzer; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; + +/** + * @author Gabor Bergmann + * + */ +class JoinCandidate { + private QueryAnalyzer analyzer; + + SubPlan primary; + SubPlan secondary; + + Set varPrimary; + Set varSecondary; + Set varCommon; + + List consPrimary; + List consSecondary; + + + JoinCandidate(SubPlan primary, SubPlan secondary, QueryAnalyzer analyzer) { + super(); + this.primary = primary; + this.secondary = secondary; + this.analyzer = analyzer; + + varPrimary = getPrimary().getVisibleVariables(); + varSecondary = getSecondary().getVisibleVariables(); + varCommon = CollectionsFactory.createSet(varPrimary); + varCommon.retainAll(varSecondary); + + consPrimary = new ArrayList(primary.getAllEnforcedConstraints()); + Collections.sort(consPrimary, TieBreaker.CONSTRAINT_COMPARATOR); + consSecondary = new ArrayList(secondary.getAllEnforcedConstraints()); + Collections.sort(consSecondary, TieBreaker.CONSTRAINT_COMPARATOR); + } + + + + /** + * @return the a + */ + public SubPlan getPrimary() { + return primary; + } + + /** + * @return the b + */ + public SubPlan getSecondary() { + return secondary; + } + + public SubPlan getJoinedPlan(SubPlanFactory factory) { + // check special cases first + if (isTrivial()) + return primary; + if (isSubsumption()) + return + (consPrimary.size() > consSecondary.size()) ? primary : secondary; + + + // default case + return factory.createSubPlan(new PJoin(), primary, secondary); + } + + @Override + public String toString() { + return primary.toString() + " |x| " + secondary.toString(); + } + + /** + * @return the varPrimary + */ + public Set getVarPrimary() { + return varPrimary; + } + + /** + * @return the varSecondary + */ + public Set getVarSecondary() { + return varSecondary; + } + + /** + * @return constraints of primary, sorted according to {@link TieBreaker#CONSTRAINT_COMPARATOR}. + */ + public List getConsPrimary() { + return consPrimary; + } + /** + * @return constraints of secondary, sorted according to {@link TieBreaker#CONSTRAINT_COMPARATOR}. + */ + public List getConsSecondary() { + return consSecondary; + } + + + + public boolean isTrivial() { + return getPrimary().equals(getSecondary()); + } + + public boolean isSubsumption() { + return consPrimary.containsAll(consSecondary) || consSecondary.containsAll(consPrimary); + } + + public boolean isCheckOnly() { + return varPrimary.containsAll(varSecondary) || varSecondary.containsAll(varPrimary); + } + + public boolean isDescartes() { + return Collections.disjoint(varPrimary, varSecondary); + } + + private Boolean heath; + + // it is a Heath-join iff common variables functionally determine either all primary or all secondary variables + public boolean isHeath() { + if (heath == null) { + Set union = Stream.concat( + primary.getAllEnforcedConstraints().stream(), + secondary.getAllEnforcedConstraints().stream() + ).collect(Collectors.toSet()); + Map, Set> dependencies = + analyzer.getFunctionalDependencies(union, false); + // does varCommon determine either varPrimary or varSecondary? + Set varCommonClosure = FunctionalDependencyHelper.closureOf(varCommon, dependencies); + + heath = varCommonClosure.containsAll(varPrimary) || varCommonClosure.containsAll(varSecondary); + } + return heath; + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/quasitree/JoinOrderingHeuristics.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/quasitree/JoinOrderingHeuristics.java new file mode 100644 index 00000000..0ea7c1d9 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/quasitree/JoinOrderingHeuristics.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.construction.quasitree; + +import java.util.Comparator; + +import tools.refinery.viatra.runtime.rete.util.Options; +import tools.refinery.viatra.runtime.rete.util.OrderingCompareAgent; + +/** + * @author Gabor Bergmann + * + */ +public class JoinOrderingHeuristics implements Comparator { + + @Override + public int compare(JoinCandidate jc1, JoinCandidate jc2) { + return new OrderingCompareAgent(jc1, jc2) { + @Override + protected void doCompare() { + swallowBoolean(true && consider(preferTrue(a.isTrivial(), b.isTrivial())) + && consider(preferTrue(a.isSubsumption(), b.isSubsumption())) + && consider(preferTrue(a.isCheckOnly(), b.isCheckOnly())) + && consider( + Options.functionalDependencyOption == Options.FunctionalDependencyOption.OFF ? + dontCare() : + preferTrue(a.isHeath(), b.isHeath()) + ) + && consider(preferFalse(a.isDescartes(), b.isDescartes())) + + // TODO main heuristic decisions + + // tie breaking + && consider(preferLess(a.getConsPrimary(), b.getConsPrimary(), TieBreaker.CONSTRAINT_LIST_COMPARATOR)) + && consider(preferLess(a.getConsSecondary(), b.getConsSecondary(), TieBreaker.CONSTRAINT_LIST_COMPARATOR)) + && consider(preferLess(System.identityHashCode(a), System.identityHashCode(b)))); + } + }.compare(); + + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/quasitree/QuasiTreeLayout.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/quasitree/QuasiTreeLayout.java new file mode 100644 index 00000000..9b814376 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/quasitree/QuasiTreeLayout.java @@ -0,0 +1,205 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.construction.quasitree; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.apache.log4j.Logger; +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendHintProvider; +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.planning.IQueryPlannerStrategy; +import tools.refinery.viatra.runtime.matchers.planning.SubPlan; +import tools.refinery.viatra.runtime.matchers.planning.SubPlanFactory; +import tools.refinery.viatra.runtime.matchers.planning.helpers.BuildHelper; +import tools.refinery.viatra.runtime.matchers.planning.operations.PApply; +import tools.refinery.viatra.runtime.matchers.planning.operations.PEnumerate; +import tools.refinery.viatra.runtime.matchers.planning.operations.PProject; +import tools.refinery.viatra.runtime.matchers.planning.operations.PStart; +import tools.refinery.viatra.runtime.matchers.psystem.DeferredPConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.EnumerablePConstraint; +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.analysis.QueryAnalyzer; +import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.ConstantValue; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.rete.construction.RetePatternBuildException; +import tools.refinery.viatra.runtime.rete.util.ReteHintOptions; + +/** + * Layout ideas: see https://bugs.eclipse.org/bugs/show_bug.cgi?id=398763 + * + * @author Gabor Bergmann + * + */ +public class QuasiTreeLayout implements IQueryPlannerStrategy { + + private IQueryBackendHintProvider hintProvider; + private IQueryBackendContext backendContext; + private QueryAnalyzer queryAnalyzer; + + public QuasiTreeLayout(IQueryBackendContext backendContext) { + this(backendContext, backendContext.getHintProvider()); + } + + public QuasiTreeLayout(IQueryBackendContext backendContext, IQueryBackendHintProvider hintProvider) { + this.backendContext = backendContext; + this.hintProvider = hintProvider; + queryAnalyzer = backendContext.getQueryAnalyzer(); + } + + @Override + public SubPlan plan(PBody pSystem, Logger logger, IQueryMetaContext context) { + return new Scaffold(pSystem, logger, context).run(); + } + + public class Scaffold { + PBody pSystem; + PQuery query; + IQueryMetaContext context; + private QueryEvaluationHint hints; + //IOperationCompiler compiler; + //SubPlanProcessor planProcessor = new SubPlanProcessor(); + SubPlanFactory planFactory; + + Set deferredConstraints = null; + Set enumerableConstraints = null; + Set constantConstraints = null; + Set forefront = new LinkedHashSet(); + Logger logger; + + Scaffold(PBody pSystem, Logger logger, /*IOperationCompiler compiler,*/ IQueryMetaContext context) { + this.pSystem = pSystem; + this.logger = logger; + this.context = context; + this.planFactory = new SubPlanFactory(pSystem); + query = pSystem.getPattern(); + //this.compiler = compiler; + //planProcessor.setCompiler(compiler); + + hints = hintProvider.getQueryEvaluationHint(query); + } + + /** + * @throws ViatraQueryRuntimeException + */ + public SubPlan run() { + try { + logger.debug(String.format( + "%s: patternbody build started for %s", + getClass().getSimpleName(), + query.getFullyQualifiedName())); + + // PROCESS CONSTRAINTS + deferredConstraints = pSystem.getConstraintsOfType(DeferredPConstraint.class); + enumerableConstraints = pSystem.getConstraintsOfType(EnumerablePConstraint.class); + constantConstraints = pSystem.getConstraintsOfType(ConstantValue.class); + + for (EnumerablePConstraint enumerable : enumerableConstraints) { + SubPlan plan = planFactory.createSubPlan(new PEnumerate(enumerable)); + admitSubPlan(plan); + } + if (enumerableConstraints.isEmpty()) { // EXTREME CASE + SubPlan plan = planFactory.createSubPlan(new PStart()); + admitSubPlan(plan); + } + + // JOIN FOREFRONT PLANS WHILE POSSIBLE + while (forefront.size() > 1) { + // TODO QUASI-TREE TRIVIAL JOINS? + + List candidates = generateJoinCandidates(); + JoinOrderingHeuristics ordering = new JoinOrderingHeuristics(); + JoinCandidate selectedJoin = Collections.min(candidates, ordering); + doJoin(selectedJoin); + } + assert (forefront.size() == 1); + + // PROJECT TO PARAMETERS + SubPlan preFinalPlan = forefront.iterator().next(); + SubPlan finalPlan = planFactory.createSubPlan(new PProject(pSystem.getSymbolicParameterVariables()), preFinalPlan); + + // FINAL CHECK, whether all exported variables are present + all constraint satisfied + BuildHelper.finalCheck(pSystem, finalPlan, context); + // TODO integrate the check above in SubPlan / POperation + + logger.debug(String.format( + "%s: patternbody query plan concluded for %s as: %s", + getClass().getSimpleName(), + query.getFullyQualifiedName(), + finalPlan.toLongString())); + return finalPlan; + } catch (RetePatternBuildException ex) { + ex.setPatternDescription(query); + throw ex; + } + } + + public List generateJoinCandidates() { + List candidates = new ArrayList(); + int bIndex = 0; + for (SubPlan b : forefront) { + int aIndex = 0; + for (SubPlan a : forefront) { + if (aIndex++ >= bIndex) + break; + candidates.add(new JoinCandidate(a, b, queryAnalyzer)); + } + bIndex++; + } + return candidates; + } + + private void admitSubPlan(SubPlan plan) { + // are there any unapplied constant filters that we can apply here? + if (ReteHintOptions.prioritizeConstantFiltering.getValueOrDefault(hints)) { + for (ConstantValue constantConstraint : constantConstraints) { + if (!plan.getAllEnforcedConstraints().contains(constantConstraint) && + plan.getVisibleVariables().containsAll(constantConstraint.getAffectedVariables())) { + plan = planFactory.createSubPlan(new PApply(constantConstraint), plan); + } + } + } + // are there any variables that will not be needed anymore and are worth trimming? + // (check only if there are unenforced enumerables, so that there are still upcoming joins) +// if (Options.planTrimOption != Options.PlanTrimOption.OFF && +// !plan.getAllEnforcedConstraints().containsAll(enumerableConstraints)) { + if (true) { + final SubPlan trimmed = BuildHelper.trimUnneccessaryVariables( + planFactory, plan, true, queryAnalyzer); + plan = trimmed; + } + // are there any checkable constraints? + for (DeferredPConstraint deferred : deferredConstraints) { + if (!plan.getAllEnforcedConstraints().contains(deferred)) { + if (deferred.isReadyAt(plan, context)) { + admitSubPlan(planFactory.createSubPlan(new PApply(deferred), plan)); + return; + } + } + } + // if no checkable constraints and no unused variables + forefront.add(plan); + } + + private void doJoin(JoinCandidate selectedJoin) { + forefront.remove(selectedJoin.getPrimary()); + forefront.remove(selectedJoin.getSecondary()); + admitSubPlan(selectedJoin.getJoinedPlan(planFactory)); + } + + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/quasitree/TieBreaker.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/quasitree/TieBreaker.java new file mode 100644 index 00000000..0b955922 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/construction/quasitree/TieBreaker.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.construction.quasitree; + +import java.util.Comparator; + +import tools.refinery.viatra.runtime.matchers.psystem.PConstraint; +import tools.refinery.viatra.runtime.rete.util.LexicographicComparator; + +/** + * Class providing comparators for breaking ties somewhat more deterministically. + * @author Bergmann Gabor + * + */ +public class TieBreaker { + + private TieBreaker() {/*Utility class constructor*/} + + public static final Comparator CONSTRAINT_COMPARATOR = (arg0, arg1) -> arg0.getMonotonousID() - arg1.getMonotonousID(); + + public static final Comparator> CONSTRAINT_LIST_COMPARATOR = + new LexicographicComparator(CONSTRAINT_COMPARATOR); + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/AbstractEvaluatorNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/AbstractEvaluatorNode.java new file mode 100644 index 00000000..d32a0449 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/AbstractEvaluatorNode.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.eval; + +import java.util.Iterator; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.single.SingleInputNode; + +/** + * @author Bergmann Gabor + */ +public abstract class AbstractEvaluatorNode extends SingleInputNode implements IEvaluatorNode { + + /** + * @since 1.5 + */ + protected EvaluatorCore core; + + + /** + * @since 1.5 + */ + public AbstractEvaluatorNode(ReteContainer reteContainer, EvaluatorCore core) { + super(reteContainer); + this.core = core; + core.init(this); + } + + /** + * @since 1.5 + */ + @Override + public ReteContainer getReteContainer() { + return getContainer(); + } + + /** + * @since 1.5 + */ + @Override + public String prettyPrintTraceInfoPatternList() { + return getTraceInfoPatternsEnumerated(); + } + + /** + * @since 2.4 + */ + protected void propagateIterableUpdate(final Direction direction, final Iterable update, final Timestamp timestamp) { + final Iterator itr = update.iterator(); + while (itr.hasNext()) { + propagateUpdate(direction, itr.next(), timestamp); + } + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/EvaluatorCore.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/EvaluatorCore.java new file mode 100644 index 00000000..c45c6048 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/EvaluatorCore.java @@ -0,0 +1,180 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.eval; + +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import org.apache.log4j.Logger; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.psystem.IExpressionEvaluator; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleValueProvider; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.Sets; + +/** + * An instance of this class performs the evaluation of Java expressions. + * + * @author Bergmann Gabor + * @author Tamas Szabo + * @since 1.5 + */ +public abstract class EvaluatorCore { + + protected Logger logger; + protected IExpressionEvaluator evaluator; + /** + * @since 2.4 + */ + protected int sourceTupleWidth; + private Map parameterPositions; + protected IQueryRuntimeContext runtimeContext; + protected IEvaluatorNode evaluatorNode; + + public EvaluatorCore(final Logger logger, final IExpressionEvaluator evaluator, + final Map parameterPositions, final int sourceTupleWidth) { + this.logger = logger; + this.evaluator = evaluator; + this.parameterPositions = parameterPositions; + this.sourceTupleWidth = sourceTupleWidth; + } + + public void init(final IEvaluatorNode evaluatorNode) { + this.evaluatorNode = evaluatorNode; + this.runtimeContext = evaluatorNode.getReteContainer().getNetwork().getEngine().getRuntimeContext(); + } + + /** + * @since 2.4 + */ + public abstract Iterable performEvaluation(final Tuple input); + + protected abstract String evaluationKind(); + + public Object evaluateTerm(final Tuple input) { + // actual evaluation + Object result = null; + try { + final TupleValueProvider tupleParameters = new TupleValueProvider(runtimeContext.unwrapTuple(input), + parameterPositions); + result = evaluator.evaluateExpression(tupleParameters); + } catch (final Exception e) { + logger.warn(String.format( + "The incremental pattern matcher encountered an error during %s evaluation for pattern(s) %s over values %s. Error message: %s. (Developer note: %s in %s)", + evaluationKind(), evaluatorNode.prettyPrintTraceInfoPatternList(), prettyPrintTuple(input), + e.getMessage(), e.getClass().getSimpleName(), this.evaluatorNode), e); + result = errorResult(); + } + + return result; + } + + protected String prettyPrintTuple(final Tuple tuple) { + return tuple.toString(); + } + + protected Object errorResult() { + return null; + } + + public static class PredicateEvaluatorCore extends EvaluatorCore { + + public PredicateEvaluatorCore(final Logger logger, final IExpressionEvaluator evaluator, + final Map parameterPositions, final int sourceTupleWidth) { + super(logger, evaluator, parameterPositions, sourceTupleWidth); + } + + @Override + public Iterable performEvaluation(final Tuple input) { + final Object result = evaluateTerm(input); + if (Boolean.TRUE.equals(result)) { + return Collections.singleton(input); + } else { + return null; + } + } + + @Override + protected String evaluationKind() { + return "check()"; + } + + } + + public static class FunctionEvaluatorCore extends EvaluatorCore { + + /** + * @since 2.4 + */ + protected final boolean isUnwinding; + + public FunctionEvaluatorCore(final Logger logger, final IExpressionEvaluator evaluator, + final Map parameterPositions, final int sourceTupleWidth) { + this(logger, evaluator, parameterPositions, sourceTupleWidth, false); + } + + /** + * @since 2.4 + */ + public FunctionEvaluatorCore(final Logger logger, final IExpressionEvaluator evaluator, + final Map parameterPositions, final int sourceTupleWidth, final boolean isUnwinding) { + super(logger, evaluator, parameterPositions, sourceTupleWidth); + this.isUnwinding = isUnwinding; + } + + @Override + public Iterable performEvaluation(final Tuple input) { + final Object result = evaluateTerm(input); + if (result != null) { + if (this.isUnwinding) { + final Set resultAsSet = (result instanceof Set) ? (Set) result + : (result instanceof Iterable) ? Sets.newSet((Iterable) result) : null; + + if (resultAsSet != null) { + return () -> { + final Iterator wrapped = resultAsSet.iterator(); + return new Iterator() { + @Override + public boolean hasNext() { + return wrapped.hasNext(); + } + + @Override + public Tuple next() { + final Object next = wrapped.next(); + return Tuples.staticArityLeftInheritanceTupleOf(input, + runtimeContext.wrapElement(next)); + } + }; + }; + } else { + throw new IllegalStateException( + "This is an unwinding evaluator, which expects the evaluation result to either be a set or an iterable, but it was " + + result); + } + } else { + return Collections.singleton( + Tuples.staticArityLeftInheritanceTupleOf(input, runtimeContext.wrapElement(result))); + } + } else { + return null; + } + } + + @Override + protected String evaluationKind() { + return "eval" + (this.isUnwinding ? "Unwind" : "") + "()"; + } + + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/IEvaluatorNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/IEvaluatorNode.java new file mode 100644 index 00000000..177433ab --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/IEvaluatorNode.java @@ -0,0 +1,25 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.eval; + +import tools.refinery.viatra.runtime.rete.network.ReteContainer; + +/** + * This interface is required for the communication between the evaluation core end the evaluator node. + * @author Gabor Bergmann + * @since 1.5 + */ +public interface IEvaluatorNode { + + ReteContainer getReteContainer(); + + String prettyPrintTraceInfoPatternList(); + + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/MemorylessEvaluatorNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/MemorylessEvaluatorNode.java new file mode 100644 index 00000000..8928645c --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/MemorylessEvaluatorNode.java @@ -0,0 +1,75 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.eval; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * @author Bergmann Gabor + * + */ +public class MemorylessEvaluatorNode extends AbstractEvaluatorNode { + + /** + * @since 1.5 + */ + public MemorylessEvaluatorNode(final ReteContainer reteContainer, final EvaluatorCore core) { + super(reteContainer, core); + } + + @Override + public void pullInto(final Collection collector, final boolean flush) { + final Collection parentTuples = new ArrayList(); + propagatePullInto(parentTuples, flush); + for (final Tuple parentTuple : parentTuples) { + final Iterable output = core.performEvaluation(parentTuple); + if (output != null) { + final Iterator itr = output.iterator(); + while (itr.hasNext()) { + collector.add(itr.next()); + } + } + } + } + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + final Map> parentTuples = CollectionsFactory.createMap(); + propagatePullIntoWithTimestamp(parentTuples, flush); + for (final Entry> entry : parentTuples.entrySet()) { + final Iterable output = core.performEvaluation(entry.getKey()); + if (output != null) { + final Iterator itr = output.iterator(); + while (itr.hasNext()) { + collector.put(itr.next(), entry.getValue()); + } + } + } + } + + @Override + public void update(final Direction direction, final Tuple input, final Timestamp timestamp) { + final Iterable output = core.performEvaluation(input); + if (output != null) { + propagateIterableUpdate(direction, output, timestamp); + } + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/OutputCachingEvaluatorNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/OutputCachingEvaluatorNode.java new file mode 100644 index 00000000..40a20c4e --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/OutputCachingEvaluatorNode.java @@ -0,0 +1,311 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.eval; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.Clearable; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.Signed; +import tools.refinery.viatra.runtime.matchers.util.TimelyMemory; +import tools.refinery.viatra.runtime.matchers.util.timeline.Diff; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration.TimelineRepresentation; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.communication.timely.ResumableNode; + +/** + * An evaluator node that caches the evaluation result. This node is also capable of caching the timestamps associated + * with the result tuples if it is used in recursive differential dataflow evaluation. + * + * @author Bergmann Gabor + * @author Tamas Szabo + */ +public class OutputCachingEvaluatorNode extends AbstractEvaluatorNode implements Clearable, ResumableNode { + + /** + * @since 2.3 + */ + protected NetworkStructureChangeSensitiveLogic logic; + + /** + * @since 2.4 + */ + protected Map> outputCache; + + /** + * Maps input tuples to timestamps. It is wrong to map evaluation result to timestamps because the different input + * tuples may yield the same evaluation result. This field is null as long as this node is in a non-recursive group. + * + * @since 2.4 + */ + protected TimelyMemory memory; + + /** + * @since 2.4 + */ + protected CommunicationGroup group; + + /** + * @since 1.5 + */ + public OutputCachingEvaluatorNode(final ReteContainer reteContainer, final EvaluatorCore core) { + super(reteContainer, core); + reteContainer.registerClearable(this); + this.outputCache = CollectionsFactory.createMap(); + this.logic = createLogic(); + } + + @Override + public CommunicationGroup getCurrentGroup() { + return this.group; + } + + @Override + public void setCurrentGroup(final CommunicationGroup group) { + this.group = group; + } + + @Override + public void networkStructureChanged() { + super.networkStructureChanged(); + this.logic = createLogic(); + } + + @Override + public void clear() { + this.outputCache.clear(); + if (this.memory != null) { + this.memory.clear(); + } + } + + /** + * @since 2.3 + */ + protected NetworkStructureChangeSensitiveLogic createLogic() { + if (this.reteContainer.isTimelyEvaluation() + && this.reteContainer.getCommunicationTracker().isInRecursiveGroup(this)) { + if (this.memory == null) { + this.memory = new TimelyMemory(reteContainer.isTimelyEvaluation() && reteContainer + .getTimelyConfiguration().getTimelineRepresentation() == TimelineRepresentation.FAITHFUL); + } + return TIMELY; + } else { + return TIMELESS; + } + } + + @Override + public void pullInto(final Collection collector, final boolean flush) { + this.logic.pullInto(collector, flush); + } + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + this.logic.pullIntoWithTimeline(collector, flush); + } + + @Override + public void update(final Direction direction, final Tuple input, final Timestamp timestamp) { + this.logic.update(direction, input, timestamp); + } + + /** + * @since 2.4 + */ + @Override + public Timestamp getResumableTimestamp() { + if (this.memory == null) { + return null; + } else { + return this.memory.getResumableTimestamp(); + } + } + + /** + * @since 2.4 + */ + @Override + public void resumeAt(final Timestamp timestamp) { + this.logic.resumeAt(timestamp); + } + + /** + * @since 2.3 + */ + protected static abstract class NetworkStructureChangeSensitiveLogic { + + /** + * @since 2.4 + */ + public abstract void update(final Direction direction, final Tuple input, final Timestamp timestamp); + + public abstract void pullInto(final Collection collector, final boolean flush); + + /** + * @since 2.4 + */ + public abstract void pullIntoWithTimeline(final Map> collector, final boolean flush); + + /** + * @since 2.4 + */ + public abstract void resumeAt(final Timestamp timestamp); + + } + + private final NetworkStructureChangeSensitiveLogic TIMELESS = new NetworkStructureChangeSensitiveLogic() { + + @Override + public void resumeAt(final Timestamp timestamp) { + // there is nothing to resume in the timeless case because we do not even care about timestamps + } + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + throw new UnsupportedOperationException(); + } + + @Override + public void pullInto(final Collection collector, final boolean flush) { + for (final Iterable output : outputCache.values()) { + if (output != NORESULT) { + final Iterator itr = output.iterator(); + while (itr.hasNext()) { + collector.add(itr.next()); + } + } + } + } + + @Override + public void update(final Direction direction, final Tuple input, final Timestamp timestamp) { + if (direction == Direction.INSERT) { + final Iterable output = core.performEvaluation(input); + if (output != null) { + final Iterable previous = outputCache.put(input, output); + if (previous != null) { + throw new IllegalStateException( + String.format("Duplicate insertion of tuple %s into node %s", input, this)); + } + propagateIterableUpdate(direction, output, timestamp); + } + } else { + final Iterable output = outputCache.remove(input); + if (output != null) { + // may be null if no result was yielded + propagateIterableUpdate(direction, output, timestamp); + } + } + } + }; + + private final NetworkStructureChangeSensitiveLogic TIMELY = new NetworkStructureChangeSensitiveLogic() { + + @Override + public void resumeAt(final Timestamp timestamp) { + final Map> diffMap = memory.resumeAt(timestamp); + + for (final Entry> entry : diffMap.entrySet()) { + final Tuple input = entry.getKey(); + final Iterable output = outputCache.get(input); + if (output != NORESULT) { + for (final Signed signed : entry.getValue()) { + propagateIterableUpdate(signed.getDirection(), output, signed.getPayload()); + } + } + + if (memory.get(input) == null) { + outputCache.remove(input); + } + } + + final Timestamp nextTimestamp = memory.getResumableTimestamp(); + if (nextTimestamp != null) { + group.notifyHasMessage(mailbox, nextTimestamp); + } + } + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + for (final Entry> entry : memory.asMap().entrySet()) { + final Tuple input = entry.getKey(); + final Iterable output = outputCache.get(input); + if (output != NORESULT) { + final Timeline timestamp = entry.getValue(); + final Iterator itr = output.iterator(); + while (itr.hasNext()) { + collector.put(itr.next(), timestamp); + } + } + } + } + + @Override + public void pullInto(final Collection collector, final boolean flush) { + TIMELESS.pullInto(collector, flush); + } + + @Override + public void update(final Direction direction, final Tuple input, final Timestamp timestamp) { + if (direction == Direction.INSERT) { + Iterable output = outputCache.get(input); + if (output == null) { + output = core.performEvaluation(input); + if (output == null) { + // the evaluation result is really null + output = NORESULT; + } + outputCache.put(input, output); + } + final Diff diff = memory.put(input, timestamp); + if (output != NORESULT) { + for (final Signed signed : diff) { + propagateIterableUpdate(signed.getDirection(), output, signed.getPayload()); + } + } + } else { + final Iterable output = outputCache.get(input); + final Diff diff = memory.remove(input, timestamp); + if (memory.get(input) == null) { + outputCache.remove(input); + } + if (output != NORESULT) { + for (final Signed signed : diff) { + propagateIterableUpdate(signed.getDirection(), output, signed.getPayload()); + } + } + } + } + }; + + /** + * This field is used to represent the "null" evaluation result. This is an optimization used in the timely case + * where the same tuple may be inserted multiple times with different timestamps. This way, we can also cache if + * something evaluated to null (instead of just forgetting about the previously computed result), thus avoiding the + * need to re-run a potentially expensive evaluation. + */ + private static final Iterable NORESULT = Collections + .singleton(Tuples.staticArityFlatTupleOf(NoResult.INSTANCE)); + + private enum NoResult { + INSTANCE + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/RelationEvaluatorNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/RelationEvaluatorNode.java new file mode 100644 index 00000000..68d277e8 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/eval/RelationEvaluatorNode.java @@ -0,0 +1,183 @@ +/******************************************************************************* + * Copyright (c) 2010-2022, Tamas Szabo, GitHub + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.eval; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.psystem.IRelationEvaluator; +import tools.refinery.viatra.runtime.matchers.psystem.basicdeferred.RelationEvaluation; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Clearable; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timelines; +import tools.refinery.viatra.runtime.rete.misc.SimpleReceiver; +import tools.refinery.viatra.runtime.rete.network.ProductionNode; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.StandardNode; +import tools.refinery.viatra.runtime.rete.network.Supplier; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.single.AbstractUniquenessEnforcerNode; + +/** + * A node that operates in batch-style (see {@link Receiver#doesProcessUpdatesInBatch()} and evaluates arbitrary Java + * logic represented by an {@link IRelationEvaluator} on the input relations. This is the backing computation node of a + * {@link RelationEvaluation} constraint. + * + * @author Tamas Szabo + * @since 2.8 + */ +public class RelationEvaluatorNode extends StandardNode implements Supplier, Clearable { + + private final IRelationEvaluator evaluator; + private Set cachedOutputs; + private Supplier[] inputSuppliers; + private BatchingReceiver[] inputReceivers; + + public RelationEvaluatorNode(final ReteContainer container, final IRelationEvaluator evaluator) { + super(container); + this.evaluator = evaluator; + this.reteContainer.registerClearable(this); + } + + @Override + public void clear() { + this.cachedOutputs.clear(); + } + + public void connectToParents(final List inputSuppliers) { + this.inputSuppliers = new Supplier[inputSuppliers.size()]; + this.inputReceivers = new BatchingReceiver[inputSuppliers.size()]; + + final List inputArities = evaluator.getInputArities(); + + if (inputArities.size() != inputSuppliers.size()) { + throw new IllegalStateException(evaluator.toString() + " expects " + inputArities.size() + + " inputs, but got " + inputSuppliers.size() + " input(s)!"); + } + + for (int i = 0; i < inputSuppliers.size(); i++) { + final int currentExpectedInputArity = inputArities.get(i); + final Supplier inputSupplier = inputSuppliers.get(i); + // it is expected that the supplier is a production node because + // the corresponding constraint itself accepts a list of PQuery + if (!(inputSupplier instanceof ProductionNode)) { + throw new IllegalStateException( + evaluator.toString() + " expects each one of its suppliers to be instances of " + + ProductionNode.class.getSimpleName() + " but got an instance of " + + inputSupplier.getClass().getSimpleName() + "!"); + } + final int currentActualInputArity = ((ProductionNode) inputSupplier).getPosMapping().size(); + if (currentActualInputArity != currentExpectedInputArity) { + throw new IllegalStateException( + evaluator.toString() + " expects input arity " + currentExpectedInputArity + " at position " + i + + " but got " + currentActualInputArity + "!"); + } + final BatchingReceiver inputReceiver = new BatchingReceiver((ProductionNode) inputSupplier, + this.reteContainer); + this.inputSuppliers[i] = inputSupplier; + this.inputReceivers[i] = inputReceiver; + this.reteContainer.connectAndSynchronize(inputSupplier, inputReceiver); + reteContainer.getCommunicationTracker().registerDependency(inputReceiver, this); + } + + // initialize the output relation + final List> inputSets = new ArrayList>(); + for (final BatchingReceiver inputReceiver : this.inputReceivers) { + inputSets.add(inputReceiver.getTuples()); + } + this.cachedOutputs = evaluateRelation(inputSets); + } + + @Override + public void networkStructureChanged() { + if (this.reteContainer.getCommunicationTracker().isInRecursiveGroup(this)) { + throw new IllegalStateException(this.toString() + " cannot be used in recursive evaluation!"); + } + super.networkStructureChanged(); + } + + @Override + public void pullInto(final Collection collector, final boolean flush) { + collector.addAll(this.cachedOutputs); + } + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + final Timeline timeline = Timelines.createFrom(Timestamp.ZERO); + for (final Tuple output : this.cachedOutputs) { + collector.put(output, timeline); + } + } + + private Set evaluateRelation(final List> inputs) { + try { + return this.evaluator.evaluateRelation(inputs); + } catch (final Exception e) { + throw new IllegalStateException("Exception during the evaluation of " + this.evaluator.toString() + "!", e); + } + } + + private void batchUpdateCompleted() { + final List> inputSets = new ArrayList>(); + for (final BatchingReceiver inputReceiver : this.inputReceivers) { + inputSets.add(inputReceiver.getTuples()); + } + final Set newOutputs = evaluateRelation(inputSets); + for (final Tuple tuple : newOutputs) { + if (this.cachedOutputs != null && this.cachedOutputs.remove(tuple)) { + // already known tuple - do nothing + } else { + // newly inserted tuple + propagateUpdate(Direction.INSERT, tuple, Timestamp.ZERO); + } + } + if (this.cachedOutputs != null) { + for (final Tuple tuple : this.cachedOutputs) { + // lost tuple + propagateUpdate(Direction.DELETE, tuple, Timestamp.ZERO); + } + } + this.cachedOutputs = newOutputs; + } + + public class BatchingReceiver extends SimpleReceiver { + private final ProductionNode source; + + private BatchingReceiver(final ProductionNode source, final ReteContainer container) { + super(container); + this.source = source; + } + + private Set getTuples() { + return ((AbstractUniquenessEnforcerNode) this.source).getTuples(); + } + + @Override + public void update(final Direction direction, final Tuple updateElement, final Timestamp timestamp) { + throw new UnsupportedOperationException("This receiver only supports batch-style operation!"); + } + + @Override + public void batchUpdate(final Collection> updates, final Timestamp timestamp) { + assert Timestamp.ZERO.equals(timestamp); + // there is nothing to do here because the source production node has already updated itself + // the only thing we need to do is to issue the callback + RelationEvaluatorNode.this.batchUpdateCompleted(); + } + + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/DefaultIndexerListener.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/DefaultIndexerListener.java new file mode 100644 index 00000000..6306a482 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/DefaultIndexerListener.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.index; + +import java.lang.ref.WeakReference; + +import tools.refinery.viatra.runtime.rete.network.Node; + +public abstract class DefaultIndexerListener implements IndexerListener { + + WeakReference owner; + + public DefaultIndexerListener(Node owner) { + this.owner = new WeakReference(owner); + } + + @Override + public Node getOwner() { + return owner.get(); + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/DualInputNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/DualInputNode.java new file mode 100644 index 00000000..170ac460 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/DualInputNode.java @@ -0,0 +1,348 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.index; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.NetworkStructureChangeSensitiveNode; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.StandardNode; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp.AllZeroMap; +import tools.refinery.viatra.runtime.rete.network.delayed.DelayedConnectCommand; +import tools.refinery.viatra.runtime.rete.traceability.TraceInfo; +import tools.refinery.viatra.runtime.rete.util.Options; + +/** + * Abstract superclass for nodes with two inputs that are matched against each other. + * + * @author Gabor Bergmann + */ +public abstract class DualInputNode extends StandardNode implements NetworkStructureChangeSensitiveNode { + + /** + * @since 2.3 + */ + protected NetworkStructureChangeSensitiveLogic logic; + + public IterableIndexer getPrimarySlot() { + return primarySlot; + } + + public Indexer getSecondarySlot() { + return secondarySlot; + } + + /** + * @author Gabor Bergmann + * + */ + public enum Side { + PRIMARY, SECONDARY, BOTH; + + public Side opposite() { + switch (this) { + case PRIMARY: + return SECONDARY; + case SECONDARY: + return PRIMARY; + case BOTH: + return BOTH; + default: + return BOTH; + } + } + } + + /** + * Holds the primary input slot of this node. + */ + protected IterableIndexer primarySlot; + + /** + * Holds the secondary input slot of this node. + */ + protected Indexer secondarySlot; + + /** + * Optional complementer mask + */ + protected TupleMask complementerSecondaryMask; + + /** + * true if the primary and secondary slots coincide + */ + protected boolean coincidence; + + /** + * @param reteContainer + */ + public DualInputNode(final ReteContainer reteContainer, final TupleMask complementerSecondaryMask) { + super(reteContainer); + this.complementerSecondaryMask = complementerSecondaryMask; + this.indexerGroupCache = CollectionsFactory.createMap(); + this.refreshIndexerGroupCache(); + } + + /** + * Should be called only once, when node is initialized. + */ + public void connectToIndexers(final IterableIndexer primarySlot, final Indexer secondarySlot) { + this.primarySlot = primarySlot; + this.secondarySlot = secondarySlot; + + reteContainer.getCommunicationTracker().registerDependency(primarySlot, this); + reteContainer.getCommunicationTracker().registerDependency(secondarySlot, this); + + // attach listeners + // if there is syncing, do this after the flush done for pulling, but before syncing updates + coincidence = primarySlot.equals(secondarySlot); + + if (!coincidence) { // regular case + primarySlot.attachListener(new DefaultIndexerListener(this) { + @Override + public void notifyIndexerUpdate(final Direction direction, final Tuple updateElement, + final Tuple signature, final boolean change, final Timestamp timestamp) { + DualInputNode.this.logic.notifyUpdate(Side.PRIMARY, direction, updateElement, signature, change, + timestamp); + } + + @Override + public String toString() { + return "primary@" + DualInputNode.this; + } + }); + secondarySlot.attachListener(new DefaultIndexerListener(this) { + public void notifyIndexerUpdate(final Direction direction, final Tuple updateElement, + final Tuple signature, final boolean change, final Timestamp timestamp) { + DualInputNode.this.logic.notifyUpdate(Side.SECONDARY, direction, updateElement, signature, change, + timestamp); + } + + @Override + public String toString() { + return "secondary@" + DualInputNode.this; + } + }); + } else { // if the two slots are the same, updates have to be handled carefully + primarySlot.attachListener(new DefaultIndexerListener(this) { + public void notifyIndexerUpdate(final Direction direction, final Tuple updateElement, + final Tuple signature, final boolean change, final Timestamp timestamp) { + DualInputNode.this.logic.notifyUpdate(Side.BOTH, direction, updateElement, signature, change, + timestamp); + } + + @Override + public String toString() { + return "both@" + DualInputNode.this; + } + }); + } + + for (final Receiver receiver : getReceivers()) { + this.reteContainer.getDelayedCommandQueue() + .add(new DelayedConnectCommand(this, receiver, this.reteContainer)); + } + + // Given that connectToIndexers registers new dependencies, the networkStructureChanged() method will be called + // by the CommunicationTracker, and the implementation of that method in turn will call refreshIndexerGroupCache() anyway. + this.refreshIndexerGroupCache(); + } + + /** + * Helper: retrieves all stored substitutions from the opposite side memory. + * + * @return the collection of opposite substitutions if any, or null if none + */ + protected Collection retrieveOpposites(final Side side, final Tuple signature) { + return getSlot(side.opposite()).get(signature); + } + + /** + * @since 2.3 + */ + protected NetworkStructureChangeSensitiveLogic createLogic() { + if (this.reteContainer.isTimelyEvaluation() + && this.reteContainer.getCommunicationTracker().isInRecursiveGroup(this)) { + return createTimelyLogic(); + } else { + return createTimelessLogic(); + } + } + + /** + * Helper: unifies a left and right partial matching. + */ + protected Tuple unify(final Tuple left, final Tuple right) { + return complementerSecondaryMask.combine(left, right, Options.enableInheritance, true); + } + + @Override + public void pullInto(final Collection collector, final boolean flush) { + this.logic.pullInto(collector, flush); + } + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + this.logic.pullIntoWithTimeline(collector, flush); + } + + /** + * Helper: unifies a substitution from the specified side with another substitution from the other side. + */ + protected Tuple unify(final Side side, final Tuple ps, final Tuple opposite) { + switch (side) { + case PRIMARY: + return unify(ps, opposite); + case SECONDARY: + return unify(opposite, ps); + case BOTH: + return unify(ps, opposite); + default: + return null; + } + } + + /** + * Simulates the behavior of the node for calibration purposes only. + */ + public abstract Tuple calibrate(final Tuple primary, final Tuple secondary); + + /** + * @param complementerSecondaryMask + * the complementerSecondaryMask to set + */ + public void setComplementerSecondaryMask(final TupleMask complementerSecondaryMask) { + this.complementerSecondaryMask = complementerSecondaryMask; + } + + /** + * Retrieves the slot corresponding to the specified side. + */ + protected Indexer getSlot(final Side side) { + if (side == Side.SECONDARY) { + return secondarySlot; + } else { + return primarySlot; + } + } + + @Override + public void assignTraceInfo(final TraceInfo traceInfo) { + super.assignTraceInfo(traceInfo); + if (traceInfo.propagateToIndexerParent()) { + if (primarySlot != null) { + primarySlot.acceptPropagatedTraceInfo(traceInfo); + } + if (secondarySlot != null) { + secondarySlot.acceptPropagatedTraceInfo(traceInfo); + } + } + } + + @Override + public void networkStructureChanged() { + super.networkStructureChanged(); + this.logic = createLogic(); + this.refreshIndexerGroupCache(); + } + + /** + * @since 2.3 + */ + protected abstract NetworkStructureChangeSensitiveLogic createTimelyLogic(); + + /** + * @since 2.3 + */ + protected abstract NetworkStructureChangeSensitiveLogic createTimelessLogic(); + + /** + * This map caches the result of a CommunicationTracker.areInSameGroup(indexer, this) call. It does that for both + * the primary and secondary slots. This way we can avoid the lookup in the getWithTimestamp call for each tuple. + * The cache needs to be maintained when the network structure changes. + * @since 2.3 + */ + protected Map indexerGroupCache; + + /** + * @since 2.3 + */ + protected void refreshIndexerGroupCache() { + this.indexerGroupCache.clear(); + if (this.primarySlot != null) { + this.indexerGroupCache.put(this.primarySlot, + this.reteContainer.getCommunicationTracker().areInSameGroup(this.primarySlot, this)); + } + if (this.secondarySlot != null) { + this.indexerGroupCache.put(this.secondarySlot, + this.reteContainer.getCommunicationTracker().areInSameGroup(this.secondarySlot, this)); + } + } + + /** + * @since 2.4 + */ + protected Map> getTimeline(final Tuple signature, final Indexer indexer) { + if (this.indexerGroupCache.get(indexer)) { + // recursive timely case + return indexer.getTimeline(signature); + } else { + // the indexer is in a different group, treat all of its tuples as they would have timestamp 0 + final Collection tuples = indexer.get(signature); + if (tuples == null) { + return null; + } else { + return new AllZeroMap((Set) tuples); + } + } + } + + /** + * @since 2.3 + */ + protected static abstract class NetworkStructureChangeSensitiveLogic { + + /** + * Abstract handler for update event. + * + * @param side + * The side on which the event occurred. + * @param direction + * The direction of the update. + * @param updateElement + * The partial matching that is inserted. + * @param signature + * Masked signature of updateElement. + * @param change + * Indicates whether this is/was the first/last instance of this signature in this slot. + * @since 2.4 + */ + public abstract void notifyUpdate(final Side side, final Direction direction, final Tuple updateElement, + final Tuple signature, final boolean change, final Timestamp timestamp); + + public abstract void pullInto(final Collection collector, final boolean flush); + + /** + * @since 2.4 + */ + public abstract void pullIntoWithTimeline(final Map> collector, final boolean flush); + + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/ExistenceNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/ExistenceNode.java new file mode 100644 index 00000000..275ff638 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/ExistenceNode.java @@ -0,0 +1,199 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.index; + +import java.util.Collection; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.Signed; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * Propagates all substitutions arriving at the PRIMARY slot if and only if (a matching substitution on the SECONDARY is + * present) xor (NEGATIVE). + * + * The negative parameter specifies whether this node checks for existence or non-existence. + *

    + * It is mandatory in differential dataflow evaluation that the secondary parent is in an upstream dependency component + * (so that every secondary tuple comes with zero timestamp). + * + * @author Gabor Bergmann + */ +public class ExistenceNode extends DualInputNode { + + protected boolean negative; + + /** + * @param reteContainer + * @param negative + * if false, act as existence checker, otherwise a nonexistence-checker + */ + public ExistenceNode(final ReteContainer reteContainer, final boolean negative) { + super(reteContainer, null); + this.negative = negative; + this.logic = createLogic(); + } + + @Override + public Tuple calibrate(final Tuple primary, final Tuple secondary) { + return primary; + } + + @Override + public void networkStructureChanged() { + if (this.reteContainer.isTimelyEvaluation() && this.secondarySlot != null + && this.reteContainer.getCommunicationTracker().areInSameGroup(this, this.secondarySlot)) { + throw new IllegalStateException("Secondary parent must be in an upstream dependency component!"); + } + super.networkStructureChanged(); + } + + private final NetworkStructureChangeSensitiveLogic TIMELESS = new NetworkStructureChangeSensitiveLogic() { + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + throw new UnsupportedOperationException(); + } + + @Override + public void pullInto(final Collection collector, final boolean flush) { + if (primarySlot == null || secondarySlot == null) { + return; + } + if (flush) { + reteContainer.flushUpdates(); + } + + for (final Tuple signature : primarySlot.getSignatures()) { + // primaries can not be null due to the contract of IterableIndex.getSignatures() + final Collection primaries = primarySlot.get(signature); + final Collection opposites = secondarySlot.get(signature); + if ((opposites != null) ^ negative) { + collector.addAll(primaries); + } + } + } + + @Override + public void notifyUpdate(final Side side, final Direction direction, final Tuple updateElement, + final Tuple signature, final boolean change, final Timestamp timestamp) { + // in the default case, all timestamps must be zero + assert Timestamp.ZERO.equals(timestamp); + + switch (side) { + case PRIMARY: + if ((retrieveOpposites(side, signature) != null) ^ negative) { + propagateUpdate(direction, updateElement, timestamp); + } + break; + case SECONDARY: + if (change) { + final Collection opposites = retrieveOpposites(side, signature); + if (opposites != null) { + for (final Tuple opposite : opposites) { + propagateUpdate((negative ? direction.opposite() : direction), opposite, timestamp); + } + } + } + break; + case BOTH: + // in case the slots coincide, + // negative --> always empty + // !positive --> identity + if (!negative) { + propagateUpdate(direction, updateElement, timestamp); + } + break; + } + } + }; + + private final NetworkStructureChangeSensitiveLogic TIMELY = new NetworkStructureChangeSensitiveLogic() { + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + if (primarySlot == null || secondarySlot == null) { + return; + } + if (flush) { + reteContainer.flushUpdates(); + } + + for (final Tuple signature : primarySlot.getSignatures()) { + // primaries can not be null due to the contract of IterableIndex.getSignatures() + final Map> primaries = getTimeline(signature, primarySlot); + // see contract: secondary must be in an upstream SCC + final Collection opposites = secondarySlot.get(signature); + if ((opposites != null) ^ negative) { + for (final Tuple primary : primaries.keySet()) { + collector.put(primary, primaries.get(primary)); + } + } + } + } + + @Override + public void pullInto(final Collection collector, final boolean flush) { + ExistenceNode.this.TIMELESS.pullInto(collector, flush); + } + + @Override + public void notifyUpdate(final Side side, final Direction direction, final Tuple updateElement, + final Tuple signature, final boolean change, final Timestamp timestamp) { + switch (side) { + case PRIMARY: { + final Collection opposites = secondarySlot.get(signature); + if ((opposites != null) ^ negative) { + propagateUpdate(direction, updateElement, timestamp); + } + break; + } + case SECONDARY: { + final Map> opposites = primarySlot.getTimeline(signature); + if (change) { + if (opposites != null) { + for (final Tuple opposite : opposites.keySet()) { + for (final Signed oppositeSigned : opposites.get(opposite).asChangeSequence()) { + final Direction product = direction.multiply(oppositeSigned.getDirection()); + propagateUpdate((negative ? product.opposite() : product), opposite, + oppositeSigned.getPayload()); + } + } + } + } + break; + } + case BOTH: + // in case the slots coincide, + // negative --> always empty + // positive --> identity + if (!negative) { + propagateUpdate(direction, updateElement, timestamp); + } + break; + } + } + }; + + @Override + protected NetworkStructureChangeSensitiveLogic createTimelessLogic() { + return this.TIMELESS; + } + + @Override + protected NetworkStructureChangeSensitiveLogic createTimelyLogic() { + return this.TIMELY; + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/GenericProjectionIndexer.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/GenericProjectionIndexer.java new file mode 100644 index 00000000..3de10def --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/GenericProjectionIndexer.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.index; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * A generic Indexer capable of indexing along any valid TupleMask. Does not keep track of parents, because will not + * ever pull parents. + * + * @author Gabor Bergmann + * + */ +public class GenericProjectionIndexer extends IndexerWithMemory implements ProjectionIndexer { + + public GenericProjectionIndexer(ReteContainer reteContainer, TupleMask mask) { + super(reteContainer, mask); + } + + @Override + protected void update(Direction direction, Tuple updateElement, Tuple signature, boolean change, + Timestamp timestamp) { + propagate(direction, updateElement, signature, change, timestamp); + } + + @Override + public Collection get(Tuple signature) { + return memory.get(signature); + } + + @Override + public Map> getTimeline(Tuple signature) { + return memory.getWithTimeline(signature); + } + + @Override + public Iterator iterator() { + return memory.iterator(); + } + + @Override + public Iterable getSignatures() { + return memory.getSignatures(); + } + + /** + * @since 2.0 + */ + @Override + public int getBucketCount() { + return memory.getKeysetSize(); + } + + @Override + public Receiver getActiveNode() { + return this; + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/IdentityIndexer.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/IdentityIndexer.java new file mode 100644 index 00000000..6c158f2c --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/IdentityIndexer.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * Copyright (c) 2004-2012 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.index; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.Supplier; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * Defines an abstract trivial indexer that identically projects the contents of some stateful node, and can therefore + * save space. Can only exist in connection with a stateful store, and must be operated by another node (the active + * node). Do not attach parents directly! + * + * @author Gabor Bergmann + * @noimplement Rely on the provided implementations + * @noreference Use only via standard Node and Indexer interfaces + * @noinstantiate This class is not intended to be instantiated by clients. + */ +public abstract class IdentityIndexer extends SpecializedProjectionIndexer { + + protected abstract Collection getTuples(); + + public IdentityIndexer(ReteContainer reteContainer, int tupleWidth, Supplier parent, + Node activeNode, List sharedSubscriptionList) { + super(reteContainer, TupleMask.identity(tupleWidth), parent, activeNode, sharedSubscriptionList); + } + + @Override + public Collection get(Tuple signature) { + if (contains(signature)) { + return Collections.singleton(signature); + } else + return null; + } + + protected boolean contains(Tuple signature) { + return getTuples().contains(signature); + } + + @Override + public Collection getSignatures() { + return getTuples(); + } + + @Override + public int getBucketCount() { + return getTuples().size(); + } + + @Override + public Iterator iterator() { + return getTuples().iterator(); + } + + @Override + public void propagateToListener(IndexerListener listener, Direction direction, Tuple updateElement, Timestamp timestamp) { + listener.notifyIndexerUpdate(direction, updateElement, updateElement, true, timestamp); + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/Indexer.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/Indexer.java new file mode 100644 index 00000000..fc9d7781 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/Indexer.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.index; + +import java.util.Collection; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.Supplier; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * A node that indexes incoming Tuples by their signatures as specified by a TupleMask. Notifies listeners about such + * update events through the IndexerListener. + * + * Signature tuples are created by transforming the update tuples using the mask. Tuples stored with the same signature + * are grouped together. The group or a reduction thereof is retrievable. + * + * @author Gabor Bergmann + */ +public interface Indexer extends Node { + /** + * @return the mask by which the contents are indexed. + */ + public TupleMask getMask(); + + /** + * @return the node whose contents are indexed. + */ + public Supplier getParent(); + + /** + * @return all stored tuples that conform to the specified signature, null if there are none such. CONTRACT: do not + * modify! + */ + public Collection get(Tuple signature); + + /** + * @since 2.4 + */ + default public Map> getTimeline(Tuple signature) { + throw new UnsupportedOperationException(); + } + + /** + * This indexer will be updated whenever a Rete update is sent to the active node (or an equivalent time slot + * allotted to it). The active node is typically the indexer itself, but it can be a different node such as its + * parent. + * + * @return the active node that operates this indexer + */ + public Node getActiveNode(); + + + public Collection getListeners(); + + public void attachListener(IndexerListener listener); + + public void detachListener(IndexerListener listener); + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/IndexerListener.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/IndexerListener.java new file mode 100644 index 00000000..f52b6a06 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/IndexerListener.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.index; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * A listener for update events concerning an Indexer. + * + * @author Gabor Bergmann + * + */ +public interface IndexerListener { + /** + * Notifies recipient that the indexer has just received an update. Contract: indexer already reflects the updated + * state. + * + * @param direction + * the direction of the update. + * @param updateElement + * the tuple that was updated. + * @param signature + * the signature of the tuple according to the indexer's mask. + * @param change + * whether this was the first inserted / last revoked update element with this particular signature. + * @since 2.4 + */ + void notifyIndexerUpdate(Direction direction, Tuple updateElement, Tuple signature, boolean change, Timestamp timestamp); + + Node getOwner(); +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/IndexerWithMemory.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/IndexerWithMemory.java new file mode 100644 index 00000000..a31562e9 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/IndexerWithMemory.java @@ -0,0 +1,284 @@ +/******************************************************************************* + * Copyright (c) 2004-2009 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.index; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; + +import tools.refinery.viatra.runtime.matchers.memories.MaskedTupleMemory; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.Signed; +import tools.refinery.viatra.runtime.matchers.util.timeline.Diff; +import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration.TimelineRepresentation; +import tools.refinery.viatra.runtime.rete.network.NetworkStructureChangeSensitiveNode; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.Supplier; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.communication.timely.ResumableNode; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.timeless.BehaviorChangingMailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.timely.TimelyMailbox; + +/** + * @author Gabor Bergmann + * @author Tamas Szabo + */ +public abstract class IndexerWithMemory extends StandardIndexer + implements Receiver, NetworkStructureChangeSensitiveNode, ResumableNode { + + protected MaskedTupleMemory memory; + + /** + * @since 2.3 + */ + protected NetworkStructureChangeSensitiveLogic logic; + + /** + * @since 1.6 + */ + protected final Mailbox mailbox; + + /** + * @since 2.4 + */ + protected CommunicationGroup group; + + public IndexerWithMemory(final ReteContainer reteContainer, final TupleMask mask) { + super(reteContainer, mask); + final boolean isTimely = reteContainer.isTimelyEvaluation() + && reteContainer.getCommunicationTracker().isInRecursiveGroup(this); + memory = MaskedTupleMemory.create(mask, MemoryType.SETS, this, isTimely, isTimely && reteContainer + .getTimelyConfiguration().getTimelineRepresentation() == TimelineRepresentation.FAITHFUL); + reteContainer.registerClearable(memory); + mailbox = instantiateMailbox(); + reteContainer.registerClearable(mailbox); + this.logic = createLogic(); + } + + @Override + public CommunicationGroup getCurrentGroup() { + return this.group; + } + + @Override + public void setCurrentGroup(final CommunicationGroup group) { + this.group = group; + } + + @Override + public void networkStructureChanged() { + super.networkStructureChanged(); + final boolean wasTimely = this.memory.isTimely(); + final boolean isTimely = this.reteContainer.isTimelyEvaluation() + && this.reteContainer.getCommunicationTracker().isInRecursiveGroup(this); + if (wasTimely != isTimely) { + final MaskedTupleMemory newMemory = MaskedTupleMemory.create(mask, MemoryType.SETS, this, + isTimely, isTimely && reteContainer.getTimelyConfiguration() + .getTimelineRepresentation() == TimelineRepresentation.FAITHFUL); + newMemory.initializeWith(this.memory, Timestamp.ZERO); + memory.clear(); + memory = newMemory; + } + this.logic = createLogic(); + } + + /** + * Instantiates the {@link Mailbox} of this receiver. Subclasses may override this method to provide their own + * mailbox implementation. + * + * @return the mailbox + * @since 2.0 + */ + protected Mailbox instantiateMailbox() { + if (this.reteContainer.isTimelyEvaluation()) { + return new TimelyMailbox(this, this.reteContainer); + } else { + return new BehaviorChangingMailbox(this, this.reteContainer); + } + } + + @Override + public Mailbox getMailbox() { + return this.mailbox; + } + + /** + * @since 2.0 + */ + public MaskedTupleMemory getMemory() { + return memory; + } + + @Override + public void update(final Direction direction, final Tuple updateElement, final Timestamp timestamp) { + this.logic.update(direction, updateElement, timestamp); + } + + /** + * Refined version of update + * + * @since 2.4 + */ + protected abstract void update(final Direction direction, final Tuple updateElement, final Tuple signature, + final boolean change, final Timestamp timestamp); + + @Override + public void appendParent(final Supplier supplier) { + if (parent == null) { + parent = supplier; + } else { + throw new UnsupportedOperationException("Illegal RETE edge: " + this + " already has a parent (" + parent + + ") and cannot connect to additional parent (" + supplier + "). "); + } + } + + @Override + public void removeParent(final Supplier supplier) { + if (parent == supplier) { + parent = null; + } else { + throw new IllegalArgumentException( + "Illegal RETE edge removal: the parent of " + this + " is not " + supplier); + } + } + + /** + * @since 2.4 + */ + @Override + public Collection getParents() { + return Collections.singleton(parent); + } + + /** + * @since 2.4 + */ + @Override + public void resumeAt(final Timestamp timestamp) { + this.logic.resumeAt(timestamp); + } + + /** + * @since 2.4 + */ + @Override + public Timestamp getResumableTimestamp() { + return this.memory.getResumableTimestamp(); + } + + /** + * @since 2.3 + */ + protected static abstract class NetworkStructureChangeSensitiveLogic { + + /** + * @since 2.4 + */ + public abstract void update(final Direction direction, final Tuple updateElement, final Timestamp timestamp); + + /** + * @since 2.4 + */ + public abstract void resumeAt(final Timestamp timestamp); + + } + + /** + * @since 2.3 + */ + protected NetworkStructureChangeSensitiveLogic createLogic() { + if (this.reteContainer.isTimelyEvaluation() + && this.reteContainer.getCommunicationTracker().isInRecursiveGroup(this)) { + return TIMELY; + } else { + return TIMELESS; + } + } + + private final NetworkStructureChangeSensitiveLogic TIMELY = new NetworkStructureChangeSensitiveLogic() { + + @Override + public void resumeAt(final Timestamp timestamp) { + final Iterable signatures = memory.getResumableSignatures(); + + final Map wasPresent = CollectionsFactory.createMap(); + for (final Tuple signature : signatures) { + wasPresent.put(signature, memory.isPresentAtInfinity(signature)); + } + + final Map>> signatureMap = memory.resumeAt(timestamp); + + for (final Entry>> outerEntry : signatureMap.entrySet()) { + final Tuple signature = outerEntry.getKey(); + final Map> diffMap = outerEntry.getValue(); + final boolean isPresent = memory.isPresentAtInfinity(signature); + // only send out a potential true value the first time for a given signature, then set it to false + boolean change = wasPresent.get(signature) ^ isPresent; + + for (final Entry> innerEntry : diffMap.entrySet()) { + final Tuple tuple = innerEntry.getKey(); + final Diff diffs = innerEntry.getValue(); + for (final Signed signed : diffs) { + IndexerWithMemory.this.update(signed.getDirection(), tuple, signature, change, + signed.getPayload()); + } + // change is a signature-wise flag, so it is ok to "try" to signal it for the first tuple only + change = false; + } + } + + final Timestamp nextTimestamp = memory.getResumableTimestamp(); + if (nextTimestamp != null) { + group.notifyHasMessage(mailbox, nextTimestamp); + } + } + + @Override + public void update(final Direction direction, final Tuple update, final Timestamp timestamp) { + final Tuple signature = mask.transform(update); + final boolean wasPresent = memory.isPresentAtInfinity(signature); + final Diff resultDiff = direction == Direction.INSERT + ? memory.addWithTimestamp(update, signature, timestamp) + : memory.removeWithTimestamp(update, signature, timestamp); + final boolean isPresent = memory.isPresentAtInfinity(signature); + final boolean change = wasPresent ^ isPresent; + for (final Signed signed : resultDiff) { + IndexerWithMemory.this.update(signed.getDirection(), update, signature, change, signed.getPayload()); + } + } + + }; + + private final NetworkStructureChangeSensitiveLogic TIMELESS = new NetworkStructureChangeSensitiveLogic() { + + @Override + public void update(final Direction direction, final Tuple update, final Timestamp timestamp) { + final Tuple signature = mask.transform(update); + final boolean change = direction == Direction.INSERT ? memory.add(update, signature) + : memory.remove(update, signature); + IndexerWithMemory.this.update(direction, update, signature, change, timestamp); + } + + @Override + public void resumeAt(final Timestamp timestamp) { + // there is nothing to resume in the timeless case because we do not even care about timestamps + } + + }; + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/IterableIndexer.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/IterableIndexer.java new file mode 100644 index 00000000..d6f8ef05 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/IterableIndexer.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2004-2009 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.index; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; + +/** + * An indexer that allows the iteration of all retrievable tuple groups (or reduced groups). + * + * @author Gabor Bergmann + * + */ +public interface IterableIndexer extends Indexer, Iterable { + + /** + * A view consisting of exactly those signatures whose tuple group is not empty + * @since 2.0 + */ + public Iterable getSignatures(); + + /** + * @return the number of signatures whose tuple group is not empty + * @since 2.0 + */ + public int getBucketCount(); + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/JoinNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/JoinNode.java new file mode 100644 index 00000000..9a6a0de9 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/JoinNode.java @@ -0,0 +1,193 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.index; + +import java.util.Collection; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.Signed; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * @author Gabor Bergmann + * + */ +public class JoinNode extends DualInputNode { + + public JoinNode(final ReteContainer reteContainer, final TupleMask complementerSecondaryMask) { + super(reteContainer, complementerSecondaryMask); + this.logic = createLogic(); + } + + @Override + public Tuple calibrate(final Tuple primary, final Tuple secondary) { + return unify(primary, secondary); + } + + private final NetworkStructureChangeSensitiveLogic TIMELESS = new NetworkStructureChangeSensitiveLogic() { + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + throw new UnsupportedOperationException(); + } + + @Override + public void pullInto(final Collection collector, final boolean flush) { + if (primarySlot == null || secondarySlot == null) { + return; + } + + if (flush) { + reteContainer.flushUpdates(); + } + + for (final Tuple signature : primarySlot.getSignatures()) { + // primaries can not be null due to the contract of IterableIndex.getSignatures() + final Collection primaries = primarySlot.get(signature); + final Collection opposites = secondarySlot.get(signature); + if (opposites != null) { + for (final Tuple primary : primaries) { + for (final Tuple opposite : opposites) { + collector.add(unify(primary, opposite)); + } + } + } + } + } + + @Override + public void notifyUpdate(final Side side, final Direction direction, final Tuple updateElement, + final Tuple signature, final boolean change, final Timestamp timestamp) { + // in the default case, all timestamps must be zero + assert Timestamp.ZERO.equals(timestamp); + + final Collection opposites = retrieveOpposites(side, signature); + + if (!coincidence) { + if (opposites != null) { + for (final Tuple opposite : opposites) { + propagateUpdate(direction, unify(side, updateElement, opposite), timestamp); + } + } + } else { + // compensate for coincidence of slots - this is the case when an Indexer is joined with itself + if (opposites != null) { + for (final Tuple opposite : opposites) { + if (opposite.equals(updateElement)) { + // handle self-joins of a single tuple separately + continue; + } + propagateUpdate(direction, unify(opposite, updateElement), timestamp); + propagateUpdate(direction, unify(updateElement, opposite), timestamp); + } + } + + // handle self-joins here + propagateUpdate(direction, unify(updateElement, updateElement), timestamp); + } + } + }; + + private final NetworkStructureChangeSensitiveLogic TIMELY = new NetworkStructureChangeSensitiveLogic() { + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + if (primarySlot == null || secondarySlot == null) { + return; + } + + if (flush) { + reteContainer.flushUpdates(); + } + + for (final Tuple signature : primarySlot.getSignatures()) { + // primaries can not be null due to the contract of IterableIndex.getSignatures() + final Map> primaries = getTimeline(signature, primarySlot); + final Map> opposites = getTimeline(signature, secondarySlot); + if (opposites != null) { + for (final Tuple primary : primaries.keySet()) { + for (final Tuple opposite : opposites.keySet()) { + final Timeline primaryTimeline = primaries.get(primary); + final Timeline oppositeTimeline = opposites.get(opposite); + final Timeline mergedTimeline = primaryTimeline + .mergeMultiplicative(oppositeTimeline); + if (!mergedTimeline.isEmpty()) { + collector.put(unify(primary, opposite), mergedTimeline); + } + } + } + } + } + } + + @Override + public void pullInto(final Collection collector, final boolean flush) { + JoinNode.this.TIMELESS.pullInto(collector, flush); + } + + @Override + public void notifyUpdate(final Side side, final Direction direction, final Tuple updateElement, + final Tuple signature, final boolean change, final Timestamp timestamp) { + final Indexer oppositeIndexer = getSlot(side.opposite()); + final Map> opposites = getTimeline(signature, oppositeIndexer); + + if (!coincidence) { + if (opposites != null) { + for (final Tuple opposite : opposites.keySet()) { + final Tuple unifiedTuple = unify(side, updateElement, opposite); + for (final Signed signed : opposites.get(opposite).asChangeSequence()) { + // TODO only consider signed timestamps that are greater or equal to timestamp + // plus compact the previous timestamps into at most one update + propagateUpdate(signed.getDirection().multiply(direction), unifiedTuple, + timestamp.max(signed.getPayload())); + } + } + } + } else { + // compensate for coincidence of slots - this is the case when an Indexer is joined with itself + if (opposites != null) { + for (final Tuple opposite : opposites.keySet()) { + if (opposite.equals(updateElement)) { + // handle self-joins of a single tuple separately + continue; + } + final Tuple u1 = unify(opposite, updateElement); + final Tuple u2 = unify(updateElement, opposite); + for (final Signed oppositeSigned : opposites.get(opposite).asChangeSequence()) { + final Direction updateDirection = direction.multiply(oppositeSigned.getDirection()); + final Timestamp updateTimestamp = timestamp.max(oppositeSigned.getPayload()); + propagateUpdate(updateDirection, u1, updateTimestamp); + propagateUpdate(updateDirection, u2, updateTimestamp); + } + } + } + + // handle self-join here + propagateUpdate(direction, unify(updateElement, updateElement), timestamp); + } + } + }; + + @Override + protected NetworkStructureChangeSensitiveLogic createTimelessLogic() { + return this.TIMELESS; + } + + @Override + protected NetworkStructureChangeSensitiveLogic createTimelyLogic() { + return this.TIMELY; + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/MemoryIdentityIndexer.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/MemoryIdentityIndexer.java new file mode 100644 index 00000000..59b75c33 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/MemoryIdentityIndexer.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.index; + +import java.util.Collection; +import java.util.List; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.Supplier; + +/** + * Defines a trivial indexer that identically projects the contents of a memory-equipped node, and can therefore save + * space. Can only exist in connection with a memory, and must be operated by another node. Do not attach parents + * directly! + * + * @noimplement Rely on the provided implementations + * @noreference Use only via standard Node and Indexer interfaces + * @noinstantiate This class is not intended to be instantiated by clients. + * @author Gabor Bergmann + */ + +public class MemoryIdentityIndexer extends IdentityIndexer { + + protected final Collection memory; + + /** + * @param reteContainer + * @param tupleWidth + * the width of the tuples of memoryNode + * @param memory + * the memory whose contents are to be identity-indexed + * @param parent + * the parent node that owns the memory + */ + public MemoryIdentityIndexer(ReteContainer reteContainer, int tupleWidth, Collection memory, + Supplier parent, Receiver activeNode, List sharedSubscriptionList) { + super(reteContainer, tupleWidth, parent, activeNode, sharedSubscriptionList); + this.memory = memory; + } + + @Override + protected Collection getTuples() { + return this.memory; + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/MemoryNullIndexer.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/MemoryNullIndexer.java new file mode 100644 index 00000000..204fe433 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/MemoryNullIndexer.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.index; + +import java.util.Collection; +import java.util.List; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.Supplier; + +/** + * Defines a trivial indexer that projects the contents of a memory-equipped node to the empty tuple, and can therefore + * save space. Can only exist in connection with a memory, and must be operated by another node. Do not attach parents + * directly! + * + * @author Gabor Bergmann + * @noimplement Rely on the provided implementations + * @noreference Use only via standard Node and Indexer interfaces + * @noinstantiate This class is not intended to be instantiated by clients. + */ +public class MemoryNullIndexer extends NullIndexer { + + Collection memory; + + /** + * @param reteContainer + * @param tupleWidth + * the width of the tuples of memoryNode + * @param memory + * the memory whose contents are to be null-indexed + * @param parent + * the parent node that owns the memory + */ + public MemoryNullIndexer(ReteContainer reteContainer, int tupleWidth, Collection memory, + Supplier parent, Receiver activeNode, List sharedSubscriptionList) { + super(reteContainer, tupleWidth, parent, activeNode, sharedSubscriptionList); + this.memory = memory; + } + + @Override + protected Collection getTuples() { + return this.memory; + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/NullIndexer.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/NullIndexer.java new file mode 100644 index 00000000..a875b29f --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/NullIndexer.java @@ -0,0 +1,88 @@ +/******************************************************************************* + * Copyright (c) 2004-2012 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.index; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.Supplier; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * Defines an abstract trivial indexer that projects the contents of some stateful node to the empty tuple, and can + * therefore save space. Can only exist in connection with a stateful store, and must be operated by another node (the + * active node). Do not attach parents directly! + * + * @author Gabor Bergmann + * @noimplement Rely on the provided implementations + * @noreference Use only via standard Node and Indexer interfaces + * @noinstantiate This class is not intended to be instantiated by clients. + */ +public abstract class NullIndexer extends SpecializedProjectionIndexer { + + protected abstract Collection getTuples(); + + protected static final Tuple nullSignature = Tuples.staticArityFlatTupleOf(); + protected static final Collection nullSingleton = Collections.singleton(nullSignature); + protected static final Collection emptySet = Collections.emptySet(); + + public NullIndexer(ReteContainer reteContainer, int tupleWidth, Supplier parent, Node activeNode, + List sharedSubscriptionList) { + super(reteContainer, TupleMask.linear(0, tupleWidth), parent, activeNode, sharedSubscriptionList); + } + + @Override + public Collection get(Tuple signature) { + if (nullSignature.equals(signature)) + return isEmpty() ? null : getTuples(); + else + return null; + } + + @Override + public Collection getSignatures() { + return isEmpty() ? emptySet : nullSingleton; + } + + protected boolean isEmpty() { + return getTuples().isEmpty(); + } + + protected boolean isSingleElement() { + return getTuples().size() == 1; + } + + @Override + public Iterator iterator() { + return getTuples().iterator(); + } + + @Override + public int getBucketCount() { + return getTuples().isEmpty() ? 0 : 1; + } + + @Override + public void propagateToListener(IndexerListener listener, Direction direction, Tuple updateElement, + Timestamp timestamp) { + boolean radical = (direction == Direction.DELETE && isEmpty()) + || (direction == Direction.INSERT && isSingleElement()); + listener.notifyIndexerUpdate(direction, updateElement, nullSignature, radical, timestamp); + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/OnetimeIndexer.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/OnetimeIndexer.java new file mode 100644 index 00000000..fef84bb1 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/OnetimeIndexer.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.index; + +import java.util.Collection; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.Supplier; + +/** + * @author Gabor Bergmann Indexer whose lifetime last until the first get() DO NOT connect to nodes! + */ +public class OnetimeIndexer extends GenericProjectionIndexer { + + public OnetimeIndexer(ReteContainer reteContainer, TupleMask mask) { + super(reteContainer, mask); + } + + @Override + public Collection get(Tuple signature) { + if (tools.refinery.viatra.runtime.rete.util.Options.releaseOnetimeIndexers) { + reteContainer.unregisterClearable(memory); + reteContainer.unregisterNode(this); + } + return super.get(signature); + } + + @Override + public void appendParent(Supplier supplier) { + throw new UnsupportedOperationException("onetime indexer cannot have parents"); + } + + @Override + public void attachListener(IndexerListener listener) { + throw new UnsupportedOperationException("onetime indexer cannot have listeners"); + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/ProjectionIndexer.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/ProjectionIndexer.java new file mode 100644 index 00000000..58e593d9 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/ProjectionIndexer.java @@ -0,0 +1,21 @@ +/******************************************************************************* + * Copyright (c) 2004-2009 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.index; + +/** + * An iterable indexer that receives updates from a node, and groups received tuples intact, i.e. it does not reduce + * tuple groups. + * + * @author Gabor Bergmann + * + */ +public interface ProjectionIndexer extends IterableIndexer { + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/SpecializedProjectionIndexer.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/SpecializedProjectionIndexer.java new file mode 100644 index 00000000..9c647aa9 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/SpecializedProjectionIndexer.java @@ -0,0 +1,176 @@ +/******************************************************************************* + * Copyright (c) 2004-2012 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.index; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.Supplier; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * A specialized projection indexer that can be memory-less (relying on an external source of information). + * + *

    + * All specialized projection indexers of a single node will share the same listener list, so that notification order is + * maintained (see Bug 518434). + * + * @author Gabor Bergmann + * @noimplement Rely on the provided implementations + * @noreference Use only via standard Node and Indexer interfaces + * @noinstantiate This class is not intended to be instantiated by clients. + */ +public abstract class SpecializedProjectionIndexer extends StandardIndexer implements ProjectionIndexer { + + protected Node activeNode; + protected List subscriptions; + + /** + * @since 1.7 + */ + public SpecializedProjectionIndexer(final ReteContainer reteContainer, final TupleMask mask, final Supplier parent, + final Node activeNode, final List subscriptions) { + super(reteContainer, mask); + this.parent = parent; + this.activeNode = activeNode; + this.subscriptions = subscriptions; + } + + public List getSubscriptions() { + return subscriptions; + } + + @Override + public Node getActiveNode() { + return activeNode; + } + + @Override + protected void propagate(final Direction direction, final Tuple updateElement, final Tuple signature, + final boolean change, final Timestamp timestamp) { + throw new UnsupportedOperationException(); + } + + @Override + public void attachListener(final IndexerListener listener) { + super.attachListener(listener); + final CommunicationTracker tracker = this.getCommunicationTracker(); + final IndexerListener proxy = tracker.proxifyIndexerListener(this, listener); + final ListenerSubscription subscription = new ListenerSubscription(this, proxy); + tracker.registerDependency(this, proxy.getOwner()); + // See Bug 518434 + // Must add to the first position, so that the later listeners are notified earlier. + // Thus if the beta node added as listener is also an indirect descendant of the same indexer on its opposite + // slot, + // then the beta node is connected later than its ancestor's listener, therefore it will be notified earlier, + // eliminating duplicate insertions and lost deletions that would result from fall-through update propagation + subscriptions.add(0, subscription); + } + + @Override + public void detachListener(final IndexerListener listener) { + final CommunicationTracker tracker = this.getCommunicationTracker(); + // obtain the proxy before the super call would unregister the dependency + final IndexerListener proxy = tracker.proxifyIndexerListener(this, listener); + super.detachListener(listener); + final ListenerSubscription subscription = new ListenerSubscription(this, proxy); + final boolean wasContained = subscriptions.remove(subscription); + assert wasContained; + tracker.unregisterDependency(this, proxy.getOwner()); + } + + @Override + public void networkStructureChanged() { + super.networkStructureChanged(); + final List oldSubscriptions = new ArrayList(); + oldSubscriptions.addAll(subscriptions); + subscriptions.clear(); + for (final ListenerSubscription oldSubscription : oldSubscriptions) { + // there is no need to unregister and re-register the dependency between indexer and listener + // because the owner of the listener is the same (even if it is proxified) + final CommunicationTracker tracker = this.getCommunicationTracker(); + // the subscriptions are shared, so we MUST reuse the indexer of the subscription instead of simply 'this' + final IndexerListener proxy = tracker.proxifyIndexerListener(oldSubscription.indexer, oldSubscription.listener); + final ListenerSubscription newSubscription = new ListenerSubscription(oldSubscription.indexer, proxy); + subscriptions.add(newSubscription); + } + } + + /** + * @since 2.4 + */ + public abstract void propagateToListener(IndexerListener listener, Direction direction, Tuple updateElement, + Timestamp timestamp); + + /** + * Infrastructure to share subscriptions between specialized indexers of the same parent node. + * + * @author Gabor Bergmann + * @since 1.7 + */ + public static class ListenerSubscription { + protected SpecializedProjectionIndexer indexer; + protected IndexerListener listener; + + public ListenerSubscription(SpecializedProjectionIndexer indexer, IndexerListener listener) { + super(); + this.indexer = indexer; + this.listener = listener; + } + + /** + * @since 2.4 + */ + public SpecializedProjectionIndexer getIndexer() { + return indexer; + } + + /** + * @since 2.4 + */ + public IndexerListener getListener() { + return listener; + } + + /** + * Call this from parent node. + * @since 2.4 + */ + public void propagate(Direction direction, Tuple updateElement, Timestamp timestamp) { + indexer.propagateToListener(listener, direction, updateElement, timestamp); + } + + @Override + public int hashCode() { + return Objects.hash(indexer, listener); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ListenerSubscription other = (ListenerSubscription) obj; + return Objects.equals(listener, other.listener) && Objects.equals(indexer, other.indexer); + } + + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/StandardIndexer.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/StandardIndexer.java new file mode 100644 index 00000000..9847a8dd --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/StandardIndexer.java @@ -0,0 +1,127 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.index; + +import java.util.Collection; +import java.util.List; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.BaseNode; +import tools.refinery.viatra.runtime.rete.network.NetworkStructureChangeSensitiveNode; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.Supplier; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.traceability.TraceInfo; + +/** + * An abstract standard implementation of the Indexer interface, providing common bookkeeping functionality. + * + * @author Gabor Bergmann + * + */ +public abstract class StandardIndexer extends BaseNode implements Indexer, NetworkStructureChangeSensitiveNode { + + protected Supplier parent; + private final List originalListeners; + private final List proxyListeners; + protected TupleMask mask; + + public StandardIndexer(ReteContainer reteContainer, TupleMask mask) { + super(reteContainer); + this.parent = null; + this.mask = mask; + this.originalListeners = CollectionsFactory.createObserverList(); + this.proxyListeners = CollectionsFactory.createObserverList(); + } + + /** + * @since 2.4 + */ + protected void propagate(Direction direction, Tuple updateElement, Tuple signature, boolean change, Timestamp timestamp) { + for (IndexerListener listener : proxyListeners) { + listener.notifyIndexerUpdate(direction, updateElement, signature, change, timestamp); + } + } + + @Override + public TupleMask getMask() { + return mask; + } + + @Override + public Supplier getParent() { + return parent; + } + + @Override + public void attachListener(IndexerListener listener) { + this.getCommunicationTracker().registerDependency(this, listener.getOwner()); + // obtain the proxy after registering the dependency because then the proxy reflects the new SCC structure + final IndexerListener proxy = this.getCommunicationTracker().proxifyIndexerListener(this, listener); + // See Bug 518434 + // Must add to the first position, so that the later listeners are notified earlier. + // Thus if the beta node added as listener is also an indirect descendant of the same indexer on its opposite slot, + // then the beta node is connected later than its ancestor's listener, therefore it will be notified earlier, + // eliminating duplicate insertions and lost deletions that would result from fall-through update propagation + this.originalListeners.add(0, listener); + this.proxyListeners.add(0, proxy); + } + + @Override + public void detachListener(IndexerListener listener) { + this.originalListeners.remove(listener); + IndexerListener listenerToRemove = null; + for (final IndexerListener proxyListener : this.proxyListeners) { + if (proxyListener.getOwner() == listener.getOwner()) { + listenerToRemove = proxyListener; + break; + } + } + assert listenerToRemove != null; + this.proxyListeners.remove(listenerToRemove); + this.getCommunicationTracker().unregisterDependency(this, listener.getOwner()); + } + + @Override + public void networkStructureChanged() { + this.proxyListeners.clear(); + for (final IndexerListener original : this.originalListeners) { + this.proxyListeners.add(this.getCommunicationTracker().proxifyIndexerListener(this, original)); + } + } + + @Override + public Collection getListeners() { + return proxyListeners; + } + + @Override + public ReteContainer getContainer() { + return reteContainer; + } + + @Override + protected String toStringCore() { + return super.toStringCore() + "(" + parent + "/" + mask + ")"; + } + + @Override + public void assignTraceInfo(TraceInfo traceInfo) { + super.assignTraceInfo(traceInfo); + if (traceInfo.propagateFromIndexerToSupplierParent()) + if (parent != null) + parent.acceptPropagatedTraceInfo(traceInfo); + } + + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/TransitiveClosureNodeIndexer.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/TransitiveClosureNodeIndexer.java new file mode 100644 index 00000000..b0bea8cb --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/TransitiveClosureNodeIndexer.java @@ -0,0 +1,121 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Gabor Bergmann, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.index; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.Set; + +import tools.refinery.viatra.runtime.base.itc.alg.incscc.IncSCCAlg; +import tools.refinery.viatra.runtime.matchers.tuple.MaskedTuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.single.TransitiveClosureNode; + +// UNFINISHED, not used yet +public class TransitiveClosureNodeIndexer extends StandardIndexer implements IterableIndexer { + private TransitiveClosureNode tcNode; + private IncSCCAlg tcAlg; + private Collection emptySet; + + public TransitiveClosureNodeIndexer(TupleMask mask, IncSCCAlg tcAlg, TransitiveClosureNode tcNode) { + super(tcNode.getContainer(), mask); + this.tcAlg = tcAlg; + this.tcNode = tcNode; + this.emptySet = Collections.emptySet(); + this.parent = tcNode; + } + + @Override + public Collection get(Tuple signature) { + if (signature.getSize() == mask.sourceWidth) { + if (mask.indices.length == 0) { + // mask ()/2 + return getSignatures(); + } else if (mask.indices.length == 1) { + Set retSet = CollectionsFactory.createSet(); + + // mask (0)/2 + if (mask.indices[0] == 0) { + Object source = signature.get(0); + for (Object target : tcAlg.getAllReachableTargets(source)) { + retSet.add(Tuples.staticArityFlatTupleOf(source, target)); + } + return retSet; + } + // mask (1)/2 + if (mask.indices[0] == 1) { + Object target = signature.get(1); + for (Object source : tcAlg.getAllReachableSources(target)) { + retSet.add(Tuples.staticArityFlatTupleOf(source, target)); + } + return retSet; + } + } else { + // mask (0,1)/2 + if (mask.indices[0] == 0 && mask.indices[1] == 1) { + Object source = signature.get(0); + Object target = signature.get(1); + Tuple singleton = Tuples.staticArityFlatTupleOf(source, target); + return (tcAlg.isReachable(source, target) ? Collections.singleton(singleton) : emptySet); + } + // mask (1,0)/2 + if (mask.indices[0] == 1 && mask.indices[1] == 0) { + Object source = signature.get(1); + Object target = signature.get(0); + Tuple singleton = Tuples.staticArityFlatTupleOf(source, target); + return (tcAlg.isReachable(source, target) ? Collections.singleton(singleton) : emptySet); + } + } + } + return null; + } + + @Override + public int getBucketCount() { + throw new UnsupportedOperationException(); + } + + @Override + public Collection getSignatures() { + return asTupleCollection(tcAlg.getTcRelation()); + } + + @Override + public Iterator iterator() { + return asTupleCollection(tcAlg.getTcRelation()).iterator(); + } + + private Collection asTupleCollection( + Collection> tuples) { + Set retSet = CollectionsFactory.createSet(); + for (tools.refinery.viatra.runtime.base.itc.alg.misc.Tuple tuple : tuples) { + retSet.add(Tuples.staticArityFlatTupleOf(tuple.getSource(), tuple.getTarget())); + } + return retSet; + } + + /** + * @since 2.4 + */ + public void propagate(Direction direction, Tuple updateElement, boolean change) { + propagate(direction, updateElement, new MaskedTuple(updateElement, mask), change, null); + } + + @Override + public Receiver getActiveNode() { + return tcNode; + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/timely/TimelyMemoryIdentityIndexer.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/timely/TimelyMemoryIdentityIndexer.java new file mode 100644 index 00000000..4319ee29 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/timely/TimelyMemoryIdentityIndexer.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.index.timely; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.TimelyMemory; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.index.IdentityIndexer; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.Supplier; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +public class TimelyMemoryIdentityIndexer extends IdentityIndexer { + + protected final TimelyMemory memory; + + public TimelyMemoryIdentityIndexer(final ReteContainer reteContainer, final int tupleWidth, + final TimelyMemory memory, final Supplier parent, final Receiver activeNode, + final List sharedSubscriptionList) { + super(reteContainer, tupleWidth, parent, activeNode, sharedSubscriptionList); + this.memory = memory; + } + + @Override + public Map> getTimeline(final Tuple signature) { + final Timeline timestamp = this.memory.get(signature); + if (timestamp != null) { + return Collections.singletonMap(signature, timestamp); + } else { + return null; + } + } + + @Override + protected Collection getTuples() { + return this.memory.getTuplesAtInfinity(); + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/timely/TimelyMemoryNullIndexer.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/timely/TimelyMemoryNullIndexer.java new file mode 100644 index 00000000..0386b006 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/index/timely/TimelyMemoryNullIndexer.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.index.timely; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.TimelyMemory; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.index.NullIndexer; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.Supplier; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +public class TimelyMemoryNullIndexer extends NullIndexer { + + protected final TimelyMemory memory; + + public TimelyMemoryNullIndexer(final ReteContainer reteContainer, final int tupleWidth, + final TimelyMemory memory, final Supplier parent, + final Receiver activeNode, final List sharedSubscriptionList) { + super(reteContainer, tupleWidth, parent, activeNode, sharedSubscriptionList); + this.memory = memory; + } + + @Override + public Map> getTimeline(final Tuple signature) { + if (nullSignature.equals(signature)) { + return isEmpty() ? null : this.memory.asMap(); + } else { + return null; + } + } + + @Override + protected Collection getTuples() { + return this.memory.getTuplesAtInfinity(); + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/DRedReteBackendFactory.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/DRedReteBackendFactory.java new file mode 100644 index 00000000..83e86fe6 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/DRedReteBackendFactory.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.matcher; + +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackend; +import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; + +/** + * A {@link ReteBackendFactory} implementation that creates {@link ReteEngine}s that use delete and re-derive + * evaluation. + * + * @author Tamas Szabo + * @since 2.2 + */ +public class DRedReteBackendFactory extends ReteBackendFactory { + + public static final DRedReteBackendFactory INSTANCE = new DRedReteBackendFactory(); + + @Override + public IQueryBackend create(IQueryBackendContext context) { + return create(context, true, null); + } + + @Override + public int hashCode() { + return DRedReteBackendFactory.class.hashCode(); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof DRedReteBackendFactory)) { + return false; + } + return true; + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/HintConfigurator.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/HintConfigurator.java new file mode 100644 index 00000000..a4fa4914 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/HintConfigurator.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.matcher; + +import java.util.HashMap; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendHintProvider; +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; + +/** + * A configurable hint provider that gathers hints for queries during runtime, and delegates defaults to an external hint provider. + * + * @author Gabor Bergmann + * @since 1.5 + */ +class HintConfigurator implements IQueryBackendHintProvider { + + private IQueryBackendHintProvider defaultHintProvider; + private Map storedHints = new HashMap(); + + public HintConfigurator(IQueryBackendHintProvider defaultHintProvider) { + this.defaultHintProvider = defaultHintProvider; + } + + @Override + public QueryEvaluationHint getQueryEvaluationHint(PQuery query) { + return defaultHintProvider.getQueryEvaluationHint(query).overrideBy(storedHints.get(query)); + } + + public void storeHint(PQuery query, QueryEvaluationHint hint) { + QueryEvaluationHint oldHint = storedHints.get(query); + if (oldHint == null) + storedHints.put(query, hint); + else + storedHints.put(query, oldHint.overrideBy(hint)); + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/IncrementalMatcherCapability.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/IncrementalMatcherCapability.java new file mode 100644 index 00000000..4c64a1a1 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/IncrementalMatcherCapability.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Grill Balázs, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.matcher; + +import tools.refinery.viatra.runtime.matchers.backend.IMatcherCapability; + +/** + * @author Grill Balázs + * @since 1.4 + * + */ +public class IncrementalMatcherCapability implements IMatcherCapability { + + @Override + public boolean canBeSubstitute(IMatcherCapability capability) { + /* + * TODO: for now, as we are only prepared for Rete and LS, we can assume that + * a matcher created with Rete can always be a substitute for a matcher created + * by any backend. + */ + return true; + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/ReteBackendFactory.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/ReteBackendFactory.java new file mode 100644 index 00000000..347cabc4 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/ReteBackendFactory.java @@ -0,0 +1,100 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.matcher; + +import tools.refinery.viatra.runtime.matchers.backend.IMatcherCapability; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackend; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactory; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendHintProvider; +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.rete.construction.plancompiler.ReteRecipeCompiler; +import tools.refinery.viatra.runtime.rete.util.Options; + +public class ReteBackendFactory implements IQueryBackendFactory { + /** + * EXPERIMENTAL + */ + protected static final int reteThreads = 0; + + /** + * @since 2.0 + */ + public static final ReteBackendFactory INSTANCE = new ReteBackendFactory(); + + /** + * @deprecated Use the static {@link #INSTANCE} field instead + */ + @Deprecated + public ReteBackendFactory() { + } + + /** + * @since 1.5 + */ + @Override + public IQueryBackend create(IQueryBackendContext context) { + return create(context, false, null); + } + + /** + * @since 2.4 + */ + public IQueryBackend create(IQueryBackendContext context, boolean deleteAndRederiveEvaluation, + TimelyConfiguration timelyConfiguration) { + ReteEngine engine; + engine = new ReteEngine(context, reteThreads, deleteAndRederiveEvaluation, timelyConfiguration); + IQueryBackendHintProvider hintConfiguration = engine.getHintConfiguration(); + ReteRecipeCompiler compiler = new ReteRecipeCompiler( + Options.builderMethod.layoutStrategy(context, hintConfiguration), context.getLogger(), + context.getRuntimeContext().getMetaContext(), context.getQueryCacheContext(), hintConfiguration, + context.getQueryAnalyzer(), deleteAndRederiveEvaluation, timelyConfiguration); + engine.setCompiler(compiler); + return engine; + } + + @Override + public Class getBackendClass() { + return ReteEngine.class; + } + + @Override + public int hashCode() { + return ReteBackendFactory.class.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof ReteBackendFactory)) { + return false; + } + return true; + } + + /** + * @since 1.4 + */ + @Override + public IMatcherCapability calculateRequiredCapability(PQuery query, QueryEvaluationHint hint) { + return new IncrementalMatcherCapability(); + } + + @Override + public boolean isCaching() { + return true; + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/ReteBackendFactoryProvider.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/ReteBackendFactoryProvider.java new file mode 100644 index 00000000..98775ab3 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/ReteBackendFactoryProvider.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.matcher; + +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactory; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactoryProvider; + +/** + * @since 2.0 + * + */ +public class ReteBackendFactoryProvider implements IQueryBackendFactoryProvider { + + @Override + public IQueryBackendFactory getFactory() { + return ReteBackendFactory.INSTANCE; + } + + @Override + public boolean isSystemDefaultEngine() { + return true; + } + + @Override + public boolean isSystemDefaultCachingBackend() { + return true; + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/ReteEngine.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/ReteEngine.java new file mode 100644 index 00000000..9bd499f4 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/ReteEngine.java @@ -0,0 +1,579 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.matcher; + +import java.lang.reflect.InvocationTargetException; +import java.util.Collection; +import java.util.LinkedList; +import java.util.Map; +import java.util.concurrent.Callable; + +import org.apache.log4j.Logger; +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackend; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactory; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendHintProvider; +import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider; +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.rete.boundary.Disconnectable; +import tools.refinery.viatra.runtime.rete.boundary.ReteBoundary; +import tools.refinery.viatra.runtime.rete.construction.RetePatternBuildException; +import tools.refinery.viatra.runtime.rete.construction.plancompiler.ReteRecipeCompiler; +import tools.refinery.viatra.runtime.rete.index.Indexer; +import tools.refinery.viatra.runtime.rete.network.Network; +import tools.refinery.viatra.runtime.rete.network.NodeProvisioner; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.traceability.RecipeTraceInfo; + +/** + * @author Gabor Bergmann + * + */ +public class ReteEngine implements IQueryBackend { + + protected Network reteNet; + protected final int reteThreads; + protected ReteBoundary boundary; + + /** + * @since 2.2 + */ + protected final boolean deleteAndRederiveEvaluation; + /** + * @since 2.4 + */ + protected final TimelyConfiguration timelyConfiguration; + + private IQueryBackendContext context; + private Logger logger; + protected IQueryRuntimeContext runtimeContext; + + protected Collection disconnectables; + + protected Map matchers; + + protected ReteRecipeCompiler compiler; + + protected final boolean parallelExecutionEnabled; // TRUE if model manipulation can go on + + private boolean disposedOrUninitialized = true; + + private HintConfigurator hintConfigurator; + + /** + * @param context + * the context of the pattern matcher, conveying all information from the outside world. + * @param reteThreads + * the number of threads to operate the RETE network with; 0 means single-threaded operation, 1 starts an + * asynchronous thread to operate the RETE net, >1 uses multiple RETE containers. + */ + public ReteEngine(IQueryBackendContext context, int reteThreads) { + this(context, reteThreads, false, null); + } + + /** + * @since 2.4 + */ + public ReteEngine(IQueryBackendContext context, int reteThreads, boolean deleteAndRederiveEvaluation, TimelyConfiguration timelyConfiguration) { + super(); + this.context = context; + this.logger = context.getLogger(); + this.runtimeContext = context.getRuntimeContext(); + this.reteThreads = reteThreads; + this.parallelExecutionEnabled = reteThreads > 0; + this.deleteAndRederiveEvaluation = deleteAndRederiveEvaluation; + this.timelyConfiguration = timelyConfiguration; + initEngine(); + this.compiler = null; + } + + /** + * @since 1.6 + */ + public IQueryBackendContext getBackendContext() { + return context; + } + + /** + * @since 2.2 + */ + public boolean isDeleteAndRederiveEvaluation() { + return this.deleteAndRederiveEvaluation; + } + + /** + * @since 2.4 + */ + public TimelyConfiguration getTimelyConfiguration() { + return this.timelyConfiguration; + } + + /** + * initializes engine components + */ + private synchronized void initEngine() { + this.disposedOrUninitialized = false; + this.disconnectables = new LinkedList(); + // this.caughtExceptions = new LinkedBlockingQueue(); + + + this.hintConfigurator = new HintConfigurator(context.getHintProvider()); + + this.reteNet = new Network(reteThreads, this); + this.boundary = new ReteBoundary(this); // prerequisite: network + + this.matchers = CollectionsFactory.createMap(); + /* this.matchersScoped = new HashMap,RetePatternMatcher>>(); */ + + // prerequisite: network, framework, boundary, disconnectables + //context.subscribeBackendForUpdates(this.boundary); + // prerequisite: boundary, disconnectables +// this.traceListener = context.subscribePatternMatcherForTraceInfluences(this); + + } + + @Override + public void flushUpdates() { + for (ReteContainer container : this.reteNet.getContainers()) { + container.deliverMessagesSingleThreaded(); + } + } + + /** + * deconstructs engine components + */ + private synchronized void deconstructEngine() { + ensureInitialized(); + reteNet.kill(); + + //context.unSubscribeBackendFromUpdates(this.boundary); + for (Disconnectable disc : disconnectables) { + disc.disconnect(); + } + + this.matchers = null; + this.disconnectables = null; + + this.reteNet = null; + this.boundary = null; + + this.hintConfigurator = null; + + // this.machineListener = new MachineListener(this); // prerequisite: + // framework, disconnectables +// this.traceListener = null; + + this.disposedOrUninitialized = true; + } + + /** + * Deconstructs the engine to get rid of it finally + */ + public void killEngine() { + deconstructEngine(); + // this.framework = null; + this.compiler = null; + this.logger = null; + } + + /** + * Resets the engine to an after-initialization phase + * + */ + public void reset() { + deconstructEngine(); + + initEngine(); + + compiler.reset(); + } + + /** + * Accesses the patternmatcher for a given pattern, constructs one if a matcher is not available yet. + * + * @pre: builder is set. + * @param query + * the pattern to be matched. + * @return a patternmatcher object that can match occurences of the given pattern. + * @throws ViatraQueryRuntimeException + * if construction fails. + */ + public synchronized RetePatternMatcher accessMatcher(final PQuery query) { + ensureInitialized(); + RetePatternMatcher matcher; + // String namespace = gtPattern.getNamespace().getName(); + // String name = gtPattern.getName(); + // String fqn = namespace + "." + name; + matcher = matchers.get(query); + if (matcher == null) { + constructionWrapper(() -> { + RecipeTraceInfo prodNode; + prodNode = boundary.accessProductionTrace(query); + + RetePatternMatcher retePatternMatcher = new RetePatternMatcher(ReteEngine.this, + prodNode); + retePatternMatcher.setTag(query); + matchers.put(query, retePatternMatcher); + return null; + }); + matcher = matchers.get(query); + } + + executeDelayedCommands(); + + return matcher; + } + + + /** + * Constructs RETE pattern matchers for a collection of patterns, if they are not available yet. Model traversal + * during the whole construction period is coalesced (which may have an effect on performance, depending on the + * matcher context). + * + * @pre: builder is set. + * @param specifications + * the patterns to be matched. + * @throws ViatraQueryRuntimeException + * if construction fails. + */ + public synchronized void buildMatchersCoalesced(final Collection specifications) { + ensureInitialized(); + constructionWrapper(() -> { + for (PQuery specification : specifications) { + boundary.accessProductionNode(specification); + } + return null; + }); + } + + /** + * @since 2.4 + */ + public T constructionWrapper(final Callable payload) { + T result = null; +// context.modelReadLock(); +// try { + if (parallelExecutionEnabled) + reteNet.getStructuralChangeLock().lock(); + try { + try { + result = runtimeContext.coalesceTraversals(() -> { + T innerResult = payload.call(); + this.executeDelayedCommands(); + return innerResult; + }); + } catch (InvocationTargetException ex) { + final Throwable cause = ex.getCause(); + if (cause instanceof RetePatternBuildException) + throw (RetePatternBuildException) cause; + if (cause instanceof RuntimeException) + throw (RuntimeException) cause; + assert (false); + } + } finally { + if (parallelExecutionEnabled) + reteNet.getStructuralChangeLock().unlock(); + reteNet.waitForReteTermination(); + } +// } finally { +// context.modelReadUnLock(); +// } + return result; + } + + // /** + // * Accesses the patternmatcher for a given pattern with additional scoping, constructs one if + // * a matcher is not available yet. + // * + // * @param gtPattern + // * the pattern to be matched. + // * @param additionalScopeMap + // * additional, optional scopes for the symbolic parameters + // * maps the position of the symbolic parameter to its additional scope (if any) + // * @pre: scope.parent is non-root, i.e. this is a nontrivial constraint + // * use the static method RetePatternMatcher.buildAdditionalScopeMap() to create from PatternCallSignature + // * @return a patternmatcher object that can match occurences of the given + // * pattern. + // * @throws PatternMatcherCompileTimeException + // * if construction fails. + // */ + // public synchronized RetePatternMatcher accessMatcherScoped(PatternDescription gtPattern, Map + // additionalScopeMap) + // throws PatternMatcherCompileTimeException { + // if (additionalScopeMap.isEmpty()) return accessMatcher(gtPattern); + // + // RetePatternMatcher matcher; + // + // Map, RetePatternMatcher> scopes = matchersScoped.get(gtPattern); + // if (scopes == null) { + // scopes = new HashMap, RetePatternMatcher>(); + // matchersScoped.put(gtPattern, scopes); + // } + // + // matcher = scopes.get(additionalScopeMap); + // if (matcher == null) { + // context.modelReadLock(); + // try { + // reteNet.getStructuralChangeLock().lock(); + // try { + // Address prodNode; + // prodNode = boundary.accessProductionScoped(gtPattern, additionalScopeMap); + // + // matcher = new RetePatternMatcher(this, prodNode); + // scopes.put(additionalScopeMap, matcher); + // } finally { + // reteNet.getStructuralChangeLock().unlock(); + // } + // } finally { + // context.modelReadUnLock(); + // } + // // reteNet.flushUpdates(); + // } + // + // return matcher; + // } + + /** + * Returns an indexer that groups the contents of this Production node by their projections to a given mask. + * Designed to be called by a RetePatternMatcher. + * + * @param production + * the production node to be indexed. + * @param mask + * the mask that defines the projection. + * @return the Indexer. + */ + synchronized Indexer accessProjection(RecipeTraceInfo production, TupleMask mask) { + ensureInitialized(); + NodeProvisioner nodeProvisioner = reteNet.getHeadContainer().getProvisioner(); + Indexer result = nodeProvisioner.peekProjectionIndexer(production, mask); + if (result == null) { + result = constructionWrapper(() -> + nodeProvisioner.accessProjectionIndexerOnetime(production, mask) + ); + } + + return result; + } + + // /** + // * Retrieves the patternmatcher for a given pattern fqn, returns null if + // the matching network hasn't been constructed yet. + // * + // * @param fqn the fully qualified name of the pattern to be matched. + // * @return the previously constructed patternmatcher object that can match + // occurences of the given pattern, or null if it doesn't exist. + // */ + // public RetePatternMatcher getMatcher(String fqn) + // { + // RetePatternMatcher matcher = matchersByFqn.get(fqn); + // if (matcher == null) + // { + // Production prodNode = boundary.getProduction(fqn); + // + // matcher = new RetePatternMatcher(this, prodNode); + // matchersByFqn.put(fqn, matcher); + // } + // + // return matcher; + // } + + /** + * @since 2.3 + */ + public void executeDelayedCommands() { + for (final ReteContainer container : this.reteNet.getContainers()) { + container.executeDelayedCommands(); + } + } + + /** + * Waits until the pattern matcher is in a steady state and output can be retrieved. + */ + public void settle() { + ensureInitialized(); + reteNet.waitForReteTermination(); + } + + /** + * Waits until the pattern matcher is in a steady state and output can be retrieved. When steady state is reached, a + * retrieval action is executed before the steady state ceases. + * + * @param action + * the action to be run when reaching the steady-state. + */ + public void settle(Runnable action) { + ensureInitialized(); + reteNet.waitForReteTermination(action); + } + + // /** + // * @return the framework + // */ + // public IFramework getFramework() { + // return framework.get(); + // } + + /** + * @return the reteNet + */ + public Network getReteNet() { + ensureInitialized(); + return reteNet; + } + + /** + * @return the boundary + */ + public ReteBoundary getBoundary() { + ensureInitialized(); + return boundary; + } + + // /** + // * @return the pattern matcher builder + // */ + // public IRetePatternBuilder getBuilder() { + // return builder; + // } + + /** + * @param builder + * the pattern matcher builder to set + */ + public void setCompiler(ReteRecipeCompiler builder) { + ensureInitialized(); + this.compiler = builder; + } + +// /** +// * @return the manipulationListener +// */ +// public IManipulationListener getManipulationListener() { +// ensureInitialized(); +// return manipulationListener; +// } + +// /** +// * @return the traceListener +// */ +// public IPredicateTraceListener geTraceListener() { +// ensureInitialized(); +// return traceListener; +// } + + /** + * @param disc + * the new Disconnectable adapter. + */ + public void addDisconnectable(Disconnectable disc) { + ensureInitialized(); + disconnectables.add(disc); + } + + /** + * @return the parallelExecutionEnabled + */ + public boolean isParallelExecutionEnabled() { + return parallelExecutionEnabled; + } + + + public Logger getLogger() { + ensureInitialized(); + return logger; + } + + public IQueryRuntimeContext getRuntimeContext() { + ensureInitialized(); + return runtimeContext; + } + + public ReteRecipeCompiler getCompiler() { + ensureInitialized(); + return compiler; + } + + // /** + // * For internal use only: logs exceptions occurring during term evaluation inside the RETE net. + // * @param e + // */ + // public void logEvaluatorException(Throwable e) { + // try { + // caughtExceptions.put(e); + // } catch (InterruptedException e1) { + // logEvaluatorException(e); + // } + // } + // /** + // * Polls the exceptions caught and logged during term evaluation by this RETE engine. + // * Recommended usage: iterate polling until null is returned. + // * + // * @return the next caught exception, or null if there are no more. + // */ + // public Throwable getNextLoggedEvaluatorException() { + // return caughtExceptions.poll(); + // } + + void ensureInitialized() { + if (disposedOrUninitialized) + throw new IllegalStateException("Trying to use a Rete engine that has been disposed or has not yet been initialized."); + + } + + @Override + public IQueryResultProvider getResultProvider(PQuery query) { + return accessMatcher(query); + } + + /** + * @since 1.4 + */ + @Override + public IQueryResultProvider getResultProvider(PQuery query, QueryEvaluationHint hints) { + hintConfigurator.storeHint(query, hints); + return accessMatcher(query); + } + + @Override + public IQueryResultProvider peekExistingResultProvider(PQuery query) { + ensureInitialized(); + return matchers.get(query); + } + + @Override + public void dispose() { + killEngine(); + } + + @Override + public boolean isCaching() { + return true; + } + + /** + * @since 1.5 + * @noreference Internal API, subject to change + */ + public IQueryBackendHintProvider getHintConfiguration() { + return hintConfigurator; + } + + @Override + public IQueryBackendFactory getFactory() { + return ReteBackendFactory.INSTANCE; + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/RetePatternMatcher.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/RetePatternMatcher.java new file mode 100644 index 00000000..1f380b45 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/RetePatternMatcher.java @@ -0,0 +1,462 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.matcher; + +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackend; +import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider; +import tools.refinery.viatra.runtime.matchers.backend.IUpdateable; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.tuple.ITuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.Accuracy; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.rete.index.Indexer; +import tools.refinery.viatra.runtime.rete.index.IterableIndexer; +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.ProductionNode; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.remote.Address; +import tools.refinery.viatra.runtime.rete.single.CallbackNode; +import tools.refinery.viatra.runtime.rete.single.TransformerNode; +import tools.refinery.viatra.runtime.rete.traceability.RecipeTraceInfo; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @author Gabor Bergmann + * + */ +public class RetePatternMatcher extends TransformerNode implements IQueryResultProvider { + + protected ReteEngine engine; + protected IQueryRuntimeContext context; + protected ProductionNode productionNode; + protected RecipeTraceInfo productionNodeTrace; + protected Map posMapping; + protected Map taggedChildren = CollectionsFactory.createMap(); + protected boolean connected = false; // is rete-wise connected to the + // production node? + + /** + * @param productionNode + * a production node that matches this pattern without any parameter bindings + * @pre: Production must be local to the head container + */ + public RetePatternMatcher(ReteEngine engine, RecipeTraceInfo productionNodeTrace) { + super(engine.getReteNet().getHeadContainer()); + this.engine = engine; + this.context = engine.getRuntimeContext(); + this.productionNodeTrace = productionNodeTrace; + final Address productionAddress = reteContainer.getProvisioner() + .getOrCreateNodeByRecipe(productionNodeTrace); + if (!reteContainer.isLocal(productionAddress)) + throw new IllegalArgumentException("@pre: Production must be local to the head container"); + this.productionNode = (ProductionNode) reteContainer.resolveLocal(productionAddress); + this.posMapping = this.productionNode.getPosMapping(); + this.reteContainer.getCommunicationTracker().registerDependency(this.productionNode, this); + } + + /** + * @since 1.6 + */ + public ProductionNode getProductionNode() { + return productionNode; + } + + public Tuple matchOneRandomly(Object[] inputMapping, boolean[] fixed) { + List allMatches = matchAll(inputMapping, fixed).collect(Collectors.toList()); + if (allMatches == null || allMatches.isEmpty()) + return null; + else + return allMatches.get((int) (Math.random() * allMatches.size())); + } + + /** + * @since 2.0 + */ + public Stream matchAll(Object[] inputMapping, boolean[] fixed) { + // retrieving the projection + TupleMask mask = TupleMask.fromKeepIndicators(fixed); + Tuple inputSignature = mask.transform(Tuples.flatTupleOf(inputMapping)); + + return matchAll(mask, inputSignature); + + } + + /** + * @since 2.0 + */ + public Stream matchAll(TupleMask mask, ITuple inputSignature) { + AllMatchFetcher fetcher = new AllMatchFetcher(engine.accessProjection(productionNodeTrace, mask), + context.wrapTuple(inputSignature.toImmutable())); + engine.reteNet.waitForReteTermination(fetcher); + return fetcher.getMatches(); + + } + + /** + * @since 2.0 + */ + public Optional matchOne(Object[] inputMapping, boolean[] fixed) { + // retrieving the projection + TupleMask mask = TupleMask.fromKeepIndicators(fixed); + Tuple inputSignature = mask.transform(Tuples.flatTupleOf(inputMapping)); + + return matchOne(mask, inputSignature); + } + + /** + * @since 2.0 + */ + public Optional matchOne(TupleMask mask, ITuple inputSignature) { + SingleMatchFetcher fetcher = new SingleMatchFetcher(engine.accessProjection(productionNodeTrace, mask), + context.wrapTuple(inputSignature.toImmutable())); + engine.reteNet.waitForReteTermination(fetcher); + return Optional.ofNullable(fetcher.getMatch()); + } + + /** + * Counts the number of occurrences of the pattern that match inputMapping on positions where fixed is true. + * + * @return the number of occurrences + */ + public int count(Object[] inputMapping, boolean[] fixed) { + TupleMask mask = TupleMask.fromKeepIndicators(fixed); + Tuple inputSignature = mask.transform(Tuples.flatTupleOf(inputMapping)); + + return count(mask, inputSignature); + } + + /** + * Counts the number of occurrences of the pattern that match inputMapping on positions where fixed is true. + * + * @return the number of occurrences + * @since 1.7 + */ + public int count(TupleMask mask, ITuple inputSignature) { + CountFetcher fetcher = new CountFetcher(engine.accessProjection(productionNodeTrace, mask), + context.wrapTuple(inputSignature.toImmutable())); + engine.reteNet.waitForReteTermination(fetcher); + + return fetcher.getCount(); + } + + /** + * Counts the number of distinct tuples attainable from the match set by projecting match tuples according to the given mask. + * + * + * @return the size of the projection + * @since 2.1 + */ + public int projectionSize(TupleMask groupMask) { + ProjectionSizeFetcher fetcher = new ProjectionSizeFetcher( + (IterableIndexer) engine.accessProjection(productionNodeTrace, groupMask)); + engine.reteNet.waitForReteTermination(fetcher); + + return fetcher.getSize(); + } + + /** + * Connects a new external receiver that will receive update notifications from now on. The receiver will + * practically connect to the production node, the added value is unwrapping the updates for external use. + * + * @param synchronize + * if true, the contents of the production node will be inserted into the receiver after the connection + * is established. + */ + public synchronized void connect(Receiver receiver, boolean synchronize) { + if (!connected) { // connect to the production node as a RETE-child + reteContainer.connect(productionNode, this); + connected = true; + } + if (synchronize) + reteContainer.connectAndSynchronize(this, receiver); + else + reteContainer.connect(this, receiver); + } + + /** + * Connects a new external receiver that will receive update notifications from now on. The receiver will + * practically connect to the production node, the added value is unwrapping the updates for external use. + * + * The external receiver will be disconnectable later based on its tag. + * + * @param tag + * an identifier to recognize the child node by. + * + * @param synchronize + * if true, the contents of the production node will be inserted into the receiver after the connection + * is established. + * + */ + public synchronized void connect(Receiver receiver, Object tag, boolean synchronize) { + taggedChildren.put(tag, receiver); + connect(receiver, synchronize); + } + + /** + * Disconnects a child node. + */ + public synchronized void disconnect(Receiver receiver) { + reteContainer.disconnect(this, receiver); + } + + /** + * Disconnects the child node that was connected by specifying the given tag. + * + * @return if a child node was found registered with this tag. + */ + public synchronized boolean disconnectByTag(Object tag) { + final Receiver receiver = taggedChildren.remove(tag); + final boolean found = receiver != null; + if (found) + disconnect(receiver); + return found; + } + + @Override + protected Tuple transform(Tuple input) { + return context.unwrapTuple(input); + } + + abstract class AbstractMatchFetcher implements Runnable { + Indexer indexer; + Tuple signature; + + public AbstractMatchFetcher(Indexer indexer, Tuple signature) { + super(); + this.indexer = indexer; + this.signature = signature; + } + + @Override + public void run() { + fetch(indexer.get(signature)); + } + + protected abstract void fetch(Collection matches); + + } + + class AllMatchFetcher extends AbstractMatchFetcher { + + public AllMatchFetcher(Indexer indexer, Tuple signature) { + super(indexer, signature); + } + + Stream matches = null; + + public Stream getMatches() { + return matches; + } + + @Override + protected void fetch(Collection matches) { + if (matches == null) + this.matches = Stream.of(); + else { + this.matches = matches.stream().map(context::unwrapTuple); + } + + } + + } + + class SingleMatchFetcher extends AbstractMatchFetcher { + + public SingleMatchFetcher(Indexer indexer, Tuple signature) { + super(indexer, signature); + } + + Tuple match = null; + + public Tuple getMatch() { + return match; + } + + @Override + protected void fetch(Collection matches) { + if (matches != null && !matches.isEmpty()) + match = context.unwrapTuple(matches.iterator().next()); + } + + // public void run() { + // Collection unscopedMatches = indexer.get(signature); + // + // // checking scopes + // if (unscopedMatches != null) { + // for (Tuple um : /* productionNode */unscopedMatches) { + // match = inputConnector.unwrapTuple(um); + // return; + // + // // Tuple ps = inputConnector.unwrapTuple(um); + // // boolean ok = true; + // // if (!ignoreScope) for (int k = 0; (k < ps.getSize()) && ok; k++) { + // // if (pcs[k].getParameterMode() == ParameterMode.INPUT) { + // // // ok = ok && (inputMapping[k]==ps.elements[k]); + // // // should now be true + // // } else // ParameterMode.OUTPUT + // // { + // // IEntity scopeParent = (IEntity) pcs[k].getParameterScope().getParent(); + // // Integer containmentMode = pcs[k].getParameterScope().getContainmentMode(); + // // if (containmentMode == Scope.BELOW) + // // ok = ok && ((IModelElement) ps.get(k)).isBelowNamespace(scopeParent); + // // else + // // /* case Scope.IN: */ + // // ok = ok && scopeParent.equals(((IModelElement) ps.get(k)).getNamespace()); + // // // note: getNamespace returns null instead of the + // // // (imaginary) modelspace root entity for top level + // // // elements; + // // // this is not a problem here as Scope.IN implies + // // // scopeParent != root. + // // + // // } + // // } + // // + // // if (ok) { + // // reteMatching = new ReteMatching(ps, posMapping); + // // return; + // // } + // } + // } + // + // } + + } + + class CountFetcher extends AbstractMatchFetcher { + + public CountFetcher(Indexer indexer, Tuple signature) { + super(indexer, signature); + } + + int count = 0; + + public int getCount() { + return count; + } + + @Override + protected void fetch(Collection matches) { + count = matches == null ? 0 : matches.size(); + } + + } + + class ProjectionSizeFetcher implements Runnable { + IterableIndexer indexer; + int size = 0; + + public ProjectionSizeFetcher(IterableIndexer indexer) { + super(); + this.indexer = indexer; + } + + @Override + public void run() { + size = indexer.getBucketCount(); + } + + public int getSize() { + return size; + } + + } + + private boolean[] notNull(Object[] parameters) { + boolean[] notNull = new boolean[parameters.length]; + for (int i = 0; i < parameters.length; ++i) + notNull[i] = parameters[i] != null; + return notNull; + } + + + + @Override + public boolean hasMatch(Object[] parameters) { + return countMatches(parameters) > 0; + } + + @Override + public boolean hasMatch(TupleMask parameterSeedMask, ITuple parameters) { + return count(parameterSeedMask, parameters) > 0; + } + + @Override + public int countMatches(Object[] parameters) { + return count(parameters, notNull(parameters)); + } + + @Override + public int countMatches(TupleMask parameterSeedMask, ITuple parameters) { + return count(parameterSeedMask, parameters); + } + + + @Override + public Optional estimateCardinality(TupleMask groupMask, Accuracy requiredAccuracy) { + return Optional.of((long)projectionSize(groupMask)); // always accurate + } + + @Override + public Optional getOneArbitraryMatch(Object[] parameters) { + return matchOne(parameters, notNull(parameters)); + } + + @Override + public Optional getOneArbitraryMatch(TupleMask parameterSeedMask, ITuple parameters) { + return matchOne(parameterSeedMask, parameters); + } + + @Override + public Stream getAllMatches(Object[] parameters) { + return matchAll(parameters, notNull(parameters)); + } + + @Override + public Stream getAllMatches(TupleMask parameterSeedMask, ITuple parameters) { + return matchAll(parameterSeedMask, parameters); + } + + @Override + public IQueryBackend getQueryBackend() { + return engine; + } + + @Override + public void addUpdateListener(final IUpdateable listener, final Object listenerTag, boolean fireNow) { + // As a listener is added as a delayed command, they should be executed to make sure everything is consistent on + // return, see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=562369 + engine.constructionWrapper(() -> { + final CallbackNode callbackNode = new CallbackNode(this.reteContainer, listener); + connect(callbackNode, listenerTag, fireNow); + return null; + }); + } + + @Override + public void removeUpdateListener(Object listenerTag) { + engine.constructionWrapper(() -> { + disconnectByTag(listenerTag); + return null; + }); + } + + public Indexer getInternalIndexer(TupleMask mask) { + return engine.accessProjection(productionNodeTrace, mask); + } +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/TimelyConfiguration.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/TimelyConfiguration.java new file mode 100644 index 00000000..876ddc99 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/TimelyConfiguration.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.matcher; + +/** + * Configuration of timely evaluation. + * + * @author Tamas Szabo + * @since 2.4 + */ +public class TimelyConfiguration { + + private final AggregatorArchitecture aggregatorArchitecture; + private final TimelineRepresentation timelineRepresentation; + + public TimelyConfiguration(final TimelineRepresentation timelineRepresentation, + final AggregatorArchitecture aggregatorArchitecture) { + this.aggregatorArchitecture = aggregatorArchitecture; + this.timelineRepresentation = timelineRepresentation; + } + + public AggregatorArchitecture getAggregatorArchitecture() { + return aggregatorArchitecture; + } + + public TimelineRepresentation getTimelineRepresentation() { + return timelineRepresentation; + } + + public enum AggregatorArchitecture { + /** + * Aggregands are copied over from lower timestamps to higher timestamps. + */ + PARALLEL, + + /** + * Aggregands are only present at the timestamp where they are inserted at. + * Only aggregate results are pushed towards higher timestamps during folding. + */ + SEQUENTIAL + } + + public enum TimelineRepresentation { + /** + * Only the first moment (timestamp) of appearance is maintained per tuple. + */ + FIRST_ONLY, + + /** + * Complete timeline (series of appearance & disappearance) is maintained per tuple. + */ + FAITHFUL + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/TimelyReteBackendFactory.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/TimelyReteBackendFactory.java new file mode 100644 index 00000000..2777f169 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/matcher/TimelyReteBackendFactory.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.matcher; + +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackend; +import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; +import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration.AggregatorArchitecture; +import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration.TimelineRepresentation; + +/** + * A {@link ReteBackendFactory} implementation that creates {@link ReteEngine}s that use non-scattered timely + * evaluation. + * + * @author Tamas Szabo + * @since 2.4 + */ +public class TimelyReteBackendFactory extends ReteBackendFactory { + + private final TimelyConfiguration configuration; + + public static final TimelyReteBackendFactory FIRST_ONLY_SEQUENTIAL = new TimelyReteBackendFactory( + new TimelyConfiguration(TimelineRepresentation.FIRST_ONLY, AggregatorArchitecture.SEQUENTIAL)); + public static final TimelyReteBackendFactory FIRST_ONLY_PARALLEL = new TimelyReteBackendFactory( + new TimelyConfiguration(TimelineRepresentation.FIRST_ONLY, AggregatorArchitecture.PARALLEL)); + public static final TimelyReteBackendFactory FAITHFUL_SEQUENTIAL = new TimelyReteBackendFactory( + new TimelyConfiguration(TimelineRepresentation.FAITHFUL, AggregatorArchitecture.SEQUENTIAL)); + public static final TimelyReteBackendFactory FAITHFUL_PARALLEL = new TimelyReteBackendFactory( + new TimelyConfiguration(TimelineRepresentation.FAITHFUL, AggregatorArchitecture.PARALLEL)); + + public TimelyReteBackendFactory(final TimelyConfiguration configuration) { + this.configuration = configuration; + } + + @Override + public IQueryBackend create(final IQueryBackendContext context) { + return create(context, false, configuration); + } + + @Override + public int hashCode() { + return TimelyReteBackendFactory.class.hashCode(); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof TimelyReteBackendFactory)) { + return false; + } + return true; + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/Bag.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/Bag.java new file mode 100644 index 00000000..4aef0f96 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/Bag.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.misc; + +import java.util.Collection; +import java.util.LinkedList; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * @author Gabor Bergmann + * + * A bag is a container that tuples can be dumped into. Does NOT propagate updates! Optimized for small contents + * size OR positive updates only. + */ +public class Bag extends SimpleReceiver { + + public Collection contents; + + public Bag(ReteContainer reteContainer) { + super(reteContainer); + contents = new LinkedList(); + } + + @Override + public void update(Direction direction, Tuple updateElement, Timestamp timestamp) { + if (direction == Direction.INSERT) + contents.add(updateElement); + else + contents.remove(updateElement); + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/ConstantNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/ConstantNode.java new file mode 100644 index 00000000..980d3eee --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/ConstantNode.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.misc; + +import java.util.Collection; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.StandardNode; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * Node that always contains a single constant Tuple + * + * @author Gabor Bergmann + */ +public class ConstantNode extends StandardNode { + + protected Tuple constant; + + /** + * @param constant + * will be wrapped using {@link IQueryRuntimeContext#wrapTuple(Tuple)} + */ + public ConstantNode(ReteContainer reteContainer, Tuple constant) { + super(reteContainer); + this.constant = reteContainer.getNetwork().getEngine().getRuntimeContext().wrapTuple(constant); + } + + @Override + public void pullInto(Collection collector, boolean flush) { + collector.add(constant); + } + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + collector.put(constant, Timestamp.INSERT_AT_ZERO_TIMELINE); + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/DefaultDeltaMonitor.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/DefaultDeltaMonitor.java new file mode 100644 index 00000000..efba3117 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/DefaultDeltaMonitor.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.misc; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.rete.network.Network; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; + +/** + * Default configuration for DeltaMonitor. + * + * @author Gabor Bergmann + * + */ +public class DefaultDeltaMonitor extends DeltaMonitor { + + /** + * @param reteContainer + */ + public DefaultDeltaMonitor(ReteContainer reteContainer) { + super(reteContainer); + } + + /** + * @param network + */ + public DefaultDeltaMonitor(Network network) { + super(network.getHeadContainer()); + } + + @Override + public Tuple statelessConvert(Tuple tuple) { + return tuple; + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/DeltaMonitor.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/DeltaMonitor.java new file mode 100644 index 00000000..82b6ecda --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/DeltaMonitor.java @@ -0,0 +1,111 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.misc; + +import java.util.Collection; +import java.util.LinkedHashSet; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Clearable; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * A monitoring object that connects to the rete network as a receiver to reflect changes since an arbitrary state + * acknowledged by the client. Match tuples are represented by a type MatchType. + * + *

    + * Usage. If a new matching is found, it appears in the matchFoundEvents collection, and disappears when that + * particular matching cannot be found anymore. If the event of finding a match has been processed by the client, it can + * be removed manually. In this case, when a previously found matching is lost, the Tuple will appear in the + * matchLostEvents collection, and disappear upon finding the same matching again. "Matching lost" events can also be + * acknowledged by removing a Tuple from the collection. If the matching is found once again, it will return to + * matchFoundEvents. + * + *

    + * Technical notes. Does NOT propagate updates! + * + * By overriding statelessConvert(), results can be stored to a MatchType. MatchType must provide equals() and + * hashCode() reflecting its contents. The default implementation (DefaultDeltaMonitor) uses Tuple as MatchType. + * + * By overriding statelessFilter(), some tuples can be filtered. + * + * @author Gabor Bergmann + * + */ +public abstract class DeltaMonitor extends SimpleReceiver implements Clearable { + + /** + * matches that are newly found + */ + public Collection matchFoundEvents; + /** + * matches that are newly lost + */ + public Collection matchLostEvents; + + /** + * @param reteContainer + */ + public DeltaMonitor(ReteContainer reteContainer) { + super(reteContainer); + matchFoundEvents = new LinkedHashSet(); + matchLostEvents = new LinkedHashSet(); + reteContainer.registerClearable(this); + } + + // /** + // * Build a delta monitor into the head container of the network. + // * + // * @param network + // */ + // public DeltaMonitor(Network network) { + // this(network.getHeadContainer()); + // } + + /** + * Override this method to provide a lightweight, stateless filter on the tuples + * + * @param tuple + * the occurrence that is to be filtered + * @return true if this tuple should be monitored, false if ignored + */ + public boolean statelessFilter(Tuple tuple) { + return true; + } + + public abstract MatchType statelessConvert(Tuple tuple); + + @Override + public void update(Direction direction, Tuple updateElement, Timestamp timestamp) { + if (statelessFilter(updateElement)) { + MatchType match = statelessConvert(updateElement); + if (direction == Direction.INSERT) { + if (!matchLostEvents.remove(match)) // either had before but + // lost + matchFoundEvents.add(match); // or brand-new + } else // revoke + { + if (!matchFoundEvents.remove(match)) // either never found + // in the first + // place + matchLostEvents.add(match); // or newly lost + } + } + } + + @Override + public void clear() { + matchFoundEvents.clear(); + matchLostEvents.clear(); + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/SimpleReceiver.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/SimpleReceiver.java new file mode 100644 index 00000000..dcf9ae78 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/misc/SimpleReceiver.java @@ -0,0 +1,109 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.misc; + +import java.util.Collection; +import java.util.Collections; + +import tools.refinery.viatra.runtime.rete.network.BaseNode; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.Supplier; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.timeless.BehaviorChangingMailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.timely.TimelyMailbox; +import tools.refinery.viatra.runtime.rete.traceability.TraceInfo; + +/** + * @author Bergmann Gabor + * + */ +public abstract class SimpleReceiver extends BaseNode implements Receiver { + + protected Supplier parent = null; + /** + * @since 1.6 + */ + protected final Mailbox mailbox; + + /** + * @param reteContainer + */ + public SimpleReceiver(ReteContainer reteContainer) { + super(reteContainer); + mailbox = instantiateMailbox(); + reteContainer.registerClearable(mailbox); + } + + /** + * Instantiates the {@link Mailbox} of this receiver. + * Subclasses may override this method to provide their own mailbox implementation. + * + * @return the mailbox + * @since 2.0 + */ + protected Mailbox instantiateMailbox() { + if (this.reteContainer.isTimelyEvaluation()) { + return new TimelyMailbox(this, this.reteContainer); + } else { + return new BehaviorChangingMailbox(this, this.reteContainer); + } + } + + @Override + public Mailbox getMailbox() { + return this.mailbox; + } + + @Override + public void appendParent(Supplier supplier) { + if (parent == null) + parent = supplier; + else + throw new UnsupportedOperationException("Illegal RETE edge: " + this + " already has a parent (" + parent + + ") and cannot connect to additional parent (" + supplier + + ") as it is not a Uniqueness Enforcer Node. "); + } + + @Override + public void removeParent(Supplier supplier) { + if (parent == supplier) + parent = null; + else + throw new IllegalArgumentException("Illegal RETE edge removal: the parent of " + this + " is not " + + supplier); + } + + @Override + public Collection getParents() { + if (parent == null) + return Collections.emptySet(); + else + return Collections.singleton(parent); + } + + /** + * Disconnects this node from the network. Can be called publicly. + * + * @pre: child nodes, if any, must already be disconnected. + */ + public void disconnectFromNetwork() { + if (parent != null) + reteContainer.disconnect(parent, this); + } + + @Override + public void assignTraceInfo(TraceInfo traceInfo) { + super.assignTraceInfo(traceInfo); + if (traceInfo.propagateFromStandardNodeToSupplierParent()) + if (parent != null) + parent.acceptPropagatedTraceInfo(traceInfo); + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/BaseNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/BaseNode.java new file mode 100644 index 00000000..2469d6bd --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/BaseNode.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.TreeSet; + +import tools.refinery.viatra.runtime.rete.traceability.PatternTraceInfo; +import tools.refinery.viatra.runtime.rete.traceability.TraceInfo; + +/** + * Base implementation for a Rete node. + * + * @author Bergmann Gabor + * + */ +public abstract class BaseNode implements Node { + + protected ReteContainer reteContainer; + protected long nodeId; + protected Object tag; + protected Set traceInfos; + + /** + * @param reteContainer + * the container to create this node in + */ + public BaseNode(ReteContainer reteContainer) { + super(); + this.reteContainer = reteContainer; + this.nodeId = reteContainer.registerNode(this); + this.traceInfos = new HashSet(); + } + + @Override + public String toString() { + if (tag != null) + return toStringCore() + "->" + getTraceInfoPatternsEnumerated() + "{" + tag.toString() + "}"; + else + return toStringCore() + "->" + getTraceInfoPatternsEnumerated(); + } + + /** + * clients should override this to append before the tag / trace indicators + */ + protected String toStringCore() { + return "[" + nodeId + "]" + getClass().getSimpleName(); + } + + @Override + public ReteContainer getContainer() { + return reteContainer; + } + + @Override + public long getNodeId() { + return nodeId; + } + + @Override + public Object getTag() { + return tag; + } + + @Override + public void setTag(Object tag) { + this.tag = tag; + } + + @Override + public Set getTraceInfos() { + return Collections.unmodifiableSet(traceInfos); + } + + @Override + public void assignTraceInfo(TraceInfo traceInfo) { + traceInfos.add(traceInfo); + traceInfo.assignNode(this); + } + + @Override + public void acceptPropagatedTraceInfo(TraceInfo traceInfo) { + assignTraceInfo(traceInfo); + } + + /** + * Descendants should use this in e.g. logging + */ + protected String getTraceInfoPatternsEnumerated() { + TreeSet patternNames = new TreeSet(); + for (TraceInfo trInfo : traceInfos) { + if (trInfo instanceof PatternTraceInfo) { + final String pName = ((PatternTraceInfo) trInfo).getPatternName(); + patternNames.add(pName); + } + } + return patternNames.toString(); + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ConnectionFactory.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ConnectionFactory.java new file mode 100644 index 00000000..6606a75d --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ConnectionFactory.java @@ -0,0 +1,170 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.rete.aggregation.IndexerBasedAggregatorNode; +import tools.refinery.viatra.runtime.rete.boundary.InputConnector; +import tools.refinery.viatra.runtime.rete.eval.RelationEvaluatorNode; +import tools.refinery.viatra.runtime.rete.index.DualInputNode; +import tools.refinery.viatra.runtime.rete.index.Indexer; +import tools.refinery.viatra.runtime.rete.index.IterableIndexer; +import tools.refinery.viatra.runtime.rete.index.ProjectionIndexer; +import tools.refinery.viatra.runtime.rete.recipes.*; +import tools.refinery.viatra.runtime.rete.remote.Address; +import tools.refinery.viatra.runtime.rete.traceability.RecipeTraceInfo; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Class responsible for connecting freshly instantiating Rete nodes to their parents. + * + * @author Bergmann Gabor + * + */ +class ConnectionFactory { + ReteContainer reteContainer; + + public ConnectionFactory(ReteContainer reteContainer) { + super(); + this.reteContainer = reteContainer; + } + + // TODO move to node implementation instead? + private boolean isStateful(ReteNodeRecipe recipe) { + return recipe instanceof ProjectionIndexerRecipe || recipe instanceof IndexerBasedAggregatorRecipe + || recipe instanceof SingleColumnAggregatorRecipe || recipe instanceof ExpressionEnforcerRecipe + || recipe instanceof TransitiveClosureRecipe || recipe instanceof ProductionRecipe + || recipe instanceof UniquenessEnforcerRecipe || recipe instanceof RelationEvaluationRecipe; + + } + + /** + * PRE: nodes for parent recipes must already be created and registered + *

    + * PRE: must not be an input node (for which {@link InputConnector} is responsible) + */ + public void connectToParents(RecipeTraceInfo recipeTrace, Node freshNode) { + final ReteNodeRecipe recipe = recipeTrace.getRecipe(); + if (recipe instanceof ConstantRecipe) { + // NO-OP + } else if (recipe instanceof InputRecipe) { + throw new IllegalArgumentException( + ConnectionFactory.class.getSimpleName() + " not intended for input connection: " + recipe); + } else if (recipe instanceof SingleParentNodeRecipe) { + final Receiver receiver = (Receiver) freshNode; + ReteNodeRecipe parentRecipe = ((SingleParentNodeRecipe) recipe).getParent(); + connectToParent(recipe, receiver, parentRecipe); + } else if (recipe instanceof RelationEvaluationRecipe) { + List parentRecipes = ((MultiParentNodeRecipe) recipe).getParents(); + List parentSuppliers = new ArrayList(); + for (final ReteNodeRecipe parentRecipe : parentRecipes) { + parentSuppliers.add(getSupplierForRecipe(parentRecipe)); + } + ((RelationEvaluatorNode) freshNode).connectToParents(parentSuppliers); + } else if (recipe instanceof BetaRecipe) { + final DualInputNode beta = (DualInputNode) freshNode; + final ArrayList parentTraces = new ArrayList( + recipeTrace.getParentRecipeTraces()); + Slots slots = avoidActiveNodeConflict(parentTraces.get(0), parentTraces.get(1)); + beta.connectToIndexers(slots.primary, slots.secondary); + } else if (recipe instanceof IndexerBasedAggregatorRecipe) { + final IndexerBasedAggregatorNode aggregator = (IndexerBasedAggregatorNode) freshNode; + final IndexerBasedAggregatorRecipe aggregatorRecipe = (IndexerBasedAggregatorRecipe) recipe; + aggregator.initializeWith((ProjectionIndexer) resolveIndexer(aggregatorRecipe.getParent())); + } else if (recipe instanceof MultiParentNodeRecipe) { + final Receiver receiver = (Receiver) freshNode; + List parentRecipes = ((MultiParentNodeRecipe) recipe).getParents(); + for (ReteNodeRecipe parentRecipe : parentRecipes) { + connectToParent(recipe, receiver, parentRecipe); + } + } + } + + private Indexer resolveIndexer(final IndexerRecipe indexerRecipe) { + final Address address = reteContainer.getNetwork().getExistingNodeByRecipe(indexerRecipe); + return (Indexer) reteContainer.resolveLocal(address); + } + + private void connectToParent(ReteNodeRecipe recipe, Receiver freshNode, ReteNodeRecipe parentRecipe) { + final Supplier parentSupplier = getSupplierForRecipe(parentRecipe); + + // special synch + if (freshNode instanceof ReinitializedNode) { + Collection tuples = new ArrayList(); + parentSupplier.pullInto(tuples, true); + ((ReinitializedNode) freshNode).reinitializeWith(tuples); + reteContainer.connect(parentSupplier, freshNode); + } else { // default case + // stateless nodes do not have to be synced with contents UNLESS they already have children (recursive + // corner case) + if (isStateful(recipe) + || ((freshNode instanceof Supplier) && !((Supplier) freshNode).getReceivers().isEmpty())) { + reteContainer.connectAndSynchronize(parentSupplier, freshNode); + } else { + // stateless node, no synch + reteContainer.connect(parentSupplier, freshNode); + } + } + } + + private Supplier getSupplierForRecipe(ReteNodeRecipe recipe) { + @SuppressWarnings("unchecked") + final Address parentAddress = (Address) reteContainer.getNetwork() + .getExistingNodeByRecipe(recipe); + final Supplier supplier = reteContainer.getProvisioner().asSupplier(parentAddress); + return supplier; + } + + /** + * If two indexers share their active node, joining them via DualInputNode is error-prone. Exception: coincidence of + * the two indexers is supported. + * + * @return a replacement for the secondary Indexers, if needed + */ + private Slots avoidActiveNodeConflict(final RecipeTraceInfo primarySlot, final RecipeTraceInfo secondarySlot) { + Slots result = new Slots() { + { + primary = (IterableIndexer) resolveIndexer((ProjectionIndexerRecipe) primarySlot.getRecipe()); + secondary = resolveIndexer((IndexerRecipe) secondarySlot.getRecipe()); + } + }; + if (activeNodeConflict(result.primary, result.secondary)) + if (result.secondary instanceof IterableIndexer) + result.secondary = resolveActiveIndexer(secondarySlot); + else + result.primary = (IterableIndexer) resolveActiveIndexer(primarySlot); + return result; + } + + private Indexer resolveActiveIndexer(final RecipeTraceInfo inactiveIndexerTrace) { + final RecipeTraceInfo activeIndexerTrace = reteContainer.getProvisioner() + .accessActiveIndexer(inactiveIndexerTrace); + reteContainer.getProvisioner().getOrCreateNodeByRecipe(activeIndexerTrace); + return resolveIndexer((ProjectionIndexerRecipe) activeIndexerTrace.getRecipe()); + } + + private static class Slots { + IterableIndexer primary; + Indexer secondary; + } + + /** + * If two indexers share their active node, joining them via DualInputNode is error-prone. Exception: coincidence of + * the two indexers is supported. + * + * @return true if there is a conflict of active nodes. + */ + private boolean activeNodeConflict(Indexer primarySlot, Indexer secondarySlot) { + return !primarySlot.equals(secondarySlot) && primarySlot.getActiveNode().equals(secondarySlot.getActiveNode()); + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/IGroupable.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/IGroupable.java new file mode 100644 index 00000000..c22b06d8 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/IGroupable.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network; + +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; + +/** + * @author Gabor Bergmann + * @since 1.7 + */ +public interface IGroupable { + + /** + * @return the current group of the mailbox + * @since 1.7 + */ + CommunicationGroup getCurrentGroup(); + + /** + * Sets the current group of the mailbox + * @since 1.7 + */ + void setCurrentGroup(CommunicationGroup group); + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Network.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Network.java new file mode 100644 index 00000000..64f59ff3 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Network.java @@ -0,0 +1,408 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.network; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.boundary.InputConnector; +import tools.refinery.viatra.runtime.rete.matcher.ReteEngine; +import tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe; +import tools.refinery.viatra.runtime.rete.remote.Address; +import tools.refinery.viatra.runtime.rete.traceability.RecipeTraceInfo; +import tools.refinery.viatra.runtime.rete.util.Options; + +/** + * @author Gabor Bergmann + * + */ +public class Network { + final int threads; + + protected ArrayList containers; + ReteContainer headContainer; + private int firstContainer = 0; + private int nextContainer = 0; + + // the following fields exist only if threads > 0 + protected Map globalTerminationCriteria = null; + protected Map reportedClocks = null; + protected Lock updateLock = null; // grab during normal update operations + protected Lock structuralChangeLock = null; // grab if the network structure + // is to + // be changed + + // Knowledge of the outside world + private ReteEngine engine; + protected NodeFactory nodeFactory; + protected InputConnector inputConnector; + + // Node and recipe administration + // incl. addresses for existing nodes by recipe (where available) + // Maintained by NodeProvisioner of each container + Map> nodesByRecipe = CollectionsFactory.createMap(); + Set recipeTraces = CollectionsFactory.createSet(); + + /** + * @throws IllegalStateException + * if no node has been constructed for the recipe + */ + public synchronized Address getExistingNodeByRecipe(ReteNodeRecipe recipe) { + final Address node = nodesByRecipe.get(recipe); + if (node == null) + throw new IllegalStateException(String.format("Rete node for recipe %s not constructed yet.", recipe)); + return node; + } + + /** + * @return null if no node has been constructed for the recipe + */ + public synchronized Address getNodeByRecipeIfExists(ReteNodeRecipe recipe) { + final Address node = nodesByRecipe.get(recipe); + return node; + } + + /** + * @param threads + * the number of threads to operate the network with; 0 means single-threaded operation, 1 starts an + * asynchronous thread to operate the RETE net, >1 uses multiple RETE containers. + */ + public Network(int threads, ReteEngine engine) { + super(); + this.threads = threads; + this.engine = engine; + this.inputConnector = new InputConnector(this); + this.nodeFactory = new NodeFactory(engine.getLogger()); + + containers = new ArrayList(); + firstContainer = (threads > 1) ? Options.firstFreeContainer : 0; // NOPMD + nextContainer = firstContainer; + + if (threads > 0) { + globalTerminationCriteria = CollectionsFactory.createMap(); + reportedClocks = CollectionsFactory.createMap(); + ReadWriteLock rwl = new ReentrantReadWriteLock(); + updateLock = rwl.readLock(); + structuralChangeLock = rwl.writeLock(); + for (int i = 0; i < threads; ++i) + containers.add(new ReteContainer(this, true)); + } else + containers.add(new ReteContainer(this, false)); + + headContainer = containers.get(0); + } + + /** + * Kills this Network along with all containers and message consumption cycles. + */ + public void kill() { + for (ReteContainer container : containers) { + container.kill(); + } + containers.clear(); + } + + /** + * Returns the head container, that is guaranteed to reside in the same JVM as the Network object. + */ + public ReteContainer getHeadContainer() { + return headContainer; + } + + /** + * Returns the next container in round-robin fashion. Configurable not to yield head container. + */ + public ReteContainer getNextContainer() { + if (nextContainer >= containers.size()) + nextContainer = firstContainer; + return containers.get(nextContainer++); + } + + /** + * Internal message delivery method. + * + * @pre threads > 0 + */ + private void sendUpdate(Address receiver, Direction direction, Tuple updateElement) { + ReteContainer affectedContainer = receiver.getContainer(); + synchronized (globalTerminationCriteria) { + long newCriterion = affectedContainer.sendUpdateToLocalAddress(receiver, direction, updateElement); + terminationCriterion(affectedContainer, newCriterion); + } + } + + /** + * Internal message delivery method for single-threaded operation + * + * @pre threads == 0 + */ + private void sendUpdateSingleThreaded(Address receiver, Direction direction, + Tuple updateElement) { + ReteContainer affectedContainer = receiver.getContainer(); + affectedContainer.sendUpdateToLocalAddressSingleThreaded(receiver, direction, updateElement); + } + + /** + * Internal message delivery method. + * + * @pre threads > 0 + */ + private void sendUpdates(Address receiver, Direction direction, + Collection updateElements) { + if (updateElements.isEmpty()) + return; + ReteContainer affectedContainer = receiver.getContainer(); + synchronized (globalTerminationCriteria) { + long newCriterion = affectedContainer.sendUpdatesToLocalAddress(receiver, direction, updateElements); + terminationCriterion(affectedContainer, newCriterion); + } + } + + /** + * Sends an update message to the receiver node, indicating a newly found or lost partial matching. The node may + * reside in any of the containers associated with this network. To be called from a user thread during normal + * operation, NOT during construction. + * + * @since 2.4 + */ + public void sendExternalUpdate(Address receiver, Direction direction, Tuple updateElement) { + if (threads > 0) { + try { + updateLock.lock(); + sendUpdate(receiver, direction, updateElement); + } finally { + updateLock.unlock(); + } + } else { + sendUpdateSingleThreaded(receiver, direction, updateElement); + // getHeadContainer(). + } + } + + /** + * Sends an update message to the receiver node, indicating a newly found or lost partial matching. The node may + * reside in any of the containers associated with this network. To be called from a user thread during + * construction. + * + * @pre: structuralChangeLock MUST be grabbed by the sequence (but not necessarily this thread, as the sequence may + * span through network calls, that's why it's not enforced here ) + * + * @return the value of the target container's clock at the time when the message was accepted into its message + * queue + * @since 2.4 + */ + public void sendConstructionUpdate(Address receiver, Direction direction, Tuple updateElement) { + // structuralChangeLock.lock(); + if (threads > 0) + sendUpdate(receiver, direction, updateElement); + else + receiver.getContainer().sendUpdateToLocalAddressSingleThreaded(receiver, direction, updateElement); + // structuralChangeLock.unlock(); + } + + /** + * Sends multiple update messages atomically to the receiver node, indicating a newly found or lost partial + * matching. The node may reside in any of the containers associated with this network. To be called from a user + * thread during construction. + * + * @pre: structuralChangeLock MUST be grabbed by the sequence (but not necessarily this thread, as the sequence may + * span through network calls, that's why it's not enforced here ) + * + * @since 2.4 + */ + public void sendConstructionUpdates(Address receiver, Direction direction, + Collection updateElements) { + // structuralChangeLock.lock(); + if (threads > 0) + sendUpdates(receiver, direction, updateElements); + else + receiver.getContainer().sendUpdatesToLocalAddressSingleThreaded(receiver, direction, updateElements); + // structuralChangeLock.unlock(); + } + + /** + * Establishes connection between a supplier and a receiver node, regardless which container they are in. Not to be + * called remotely, because this method enforces the structural lock. + * + * @param supplier + * @param receiver + * @param synchronise + * indicates whether the receiver should be synchronised to the current contents of the supplier + */ + public void connectRemoteNodes(Address supplier, Address receiver, + boolean synchronise) { + try { + if (threads > 0) + structuralChangeLock.lock(); + receiver.getContainer().connectRemoteNodes(supplier, receiver, synchronise); + } finally { + if (threads > 0) + structuralChangeLock.unlock(); + } + } + + /** + * Severs connection between a supplier and a receiver node, regardless which container they are in. Not to be + * called remotely, because this method enforces the structural lock. + * + * @param supplier + * @param receiver + * @param desynchronise + * indicates whether the current contents of the supplier should be subtracted from the receiver + */ + public void disconnectRemoteNodes(Address supplier, Address receiver, + boolean desynchronise) { + try { + if (threads > 0) + structuralChangeLock.lock(); + receiver.getContainer().disconnectRemoteNodes(supplier, receiver, desynchronise); + } finally { + if (threads > 0) + structuralChangeLock.unlock(); + } + } + + /** + * Containers use this method to report whenever they run out of messages in their queue. + * + * To be called from the thread of the reporting container. + * + * @pre threads > 0. + * @param reportingContainer + * the container reporting the emptiness of its message queue. + * @param clock + * the value of the container's clock when reporting. + * @param localTerminationCriteria + * the latest clock values this container has received from other containers since the last time it + * reported termination. + */ + void reportLocalUpdateTermination(ReteContainer reportingContainer, long clock, + Map localTerminationCriteria) { + synchronized (globalTerminationCriteria) { + for (Entry criterion : localTerminationCriteria.entrySet()) { + terminationCriterion(criterion.getKey(), criterion.getValue()); + } + + reportedClocks.put(reportingContainer, clock); + Long criterion = globalTerminationCriteria.get(reportingContainer); + if (criterion != null && criterion < clock) + globalTerminationCriteria.remove(reportingContainer); + + if (globalTerminationCriteria.isEmpty()) + globalTerminationCriteria.notifyAll(); + } + } + + /** + * @pre threads > 0 + */ + private void terminationCriterion(ReteContainer affectedContainer, long newCriterion) { + synchronized (globalTerminationCriteria) { + Long oldCriterion = globalTerminationCriteria.get(affectedContainer); + Long oldClock = reportedClocks.get(affectedContainer); + long relevantClock = oldClock == null ? 0 : oldClock; + if ((relevantClock <= newCriterion) && (oldCriterion == null || oldCriterion < newCriterion)) { + globalTerminationCriteria.put(affectedContainer, newCriterion); + } + } + } + + /** + * Waits until all rete update operations are settled in all containers. Returns immediately, if no updates are + * pending. + * + * To be called from any user thread. + */ + public void waitForReteTermination() { + if (threads > 0) { + synchronized (globalTerminationCriteria) { + while (!globalTerminationCriteria.isEmpty()) { + try { + globalTerminationCriteria.wait(); + } catch (InterruptedException e) { + + } + } + } + } else + headContainer.deliverMessagesSingleThreaded(); + } + + /** + * Waits to execute action until all rete update operations are settled in all containers. Runs action and returns + * immediately, if no updates are pending. The given action is guaranteed to be run when the terminated state still + * persists. + * + * @param action + * the action to be run when reaching the steady-state. + * + * To be called from any user thread. + */ + public void waitForReteTermination(Runnable action) { + if (threads > 0) { + synchronized (globalTerminationCriteria) { + while (!globalTerminationCriteria.isEmpty()) { + try { + globalTerminationCriteria.wait(); + } catch (InterruptedException e) { + + } + } + action.run(); + } + } else { + headContainer.deliverMessagesSingleThreaded(); + action.run(); + } + + } + + /** + * @return an unmodifiable set of known recipe traces + */ + public Set getRecipeTraces() { + return Collections.unmodifiableSet(recipeTraces); + } + + /** + * @return an unmodifiable list of containers + */ + public List getContainers() { + return Collections.unmodifiableList(containers); + } + + public Lock getStructuralChangeLock() { + return structuralChangeLock; + } + + public NodeFactory getNodeFactory() { + return nodeFactory; + } + + public InputConnector getInputConnector() { + return inputConnector; + } + + public ReteEngine getEngine() { + return engine; + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/NetworkStructureChangeSensitiveNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/NetworkStructureChangeSensitiveNode.java new file mode 100644 index 00000000..c6ba34c4 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/NetworkStructureChangeSensitiveNode.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network; + +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker; + +/** + * {@link Node}s implementing this interface are sensitive to changes in the dependency graph maintained by the + * {@link CommunicationTracker}. The {@link CommunicationTracker} notifies these nodes whenever the SCC of this node is + * affected by changes to the dependency graph. Depending on whether this node is contained in a recursive group or not, + * it may behave differently, and the {@link NetworkStructureChangeSensitiveNode#networkStructureChanged()} method can + * be used to perform changes in behavior. + * + * @author Tamas Szabo + * @since 2.3 + */ +public interface NetworkStructureChangeSensitiveNode extends Node { + + /** + * At the time of the invocation, the dependency graph has already been updated. + */ + public void networkStructureChanged(); + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Node.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Node.java new file mode 100644 index 00000000..e8ab615a --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Node.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.network; + +import java.util.Set; + +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker; +import tools.refinery.viatra.runtime.rete.traceability.TraceInfo; + +/** + * A node of a rete network, should be uniquely identified by network and nodeId. NodeId can be requested by registering + * at the Network on construction. + * + * @author Gabor Bergmann + */ +public interface Node { + /** + * @return the network this node belongs to. + */ + ReteContainer getContainer(); + + /** + * @return the identifier unique to this node within the network. + */ + long getNodeId(); + + /** + * Assigns a descriptive tag to the node + */ + void setTag(Object tag); + + /** + * @return the tag of the node + */ + Object getTag(); + + /** + * @return unmodifiable view of the list of traceability infos assigned to this node + */ + Set getTraceInfos(); + + /** + * assigns new traceability info to this node + */ + void assignTraceInfo(TraceInfo traceInfo); + /** + * accepts traceability info propagated to this node + */ + void acceptPropagatedTraceInfo(TraceInfo traceInfo); + + default CommunicationTracker getCommunicationTracker() { + return getContainer().getCommunicationTracker(); + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/NodeFactory.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/NodeFactory.java new file mode 100644 index 00000000..a40a8b7f --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/NodeFactory.java @@ -0,0 +1,375 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network; + +import org.apache.log4j.Logger; +import org.eclipse.emf.common.util.EMap; +import tools.refinery.viatra.runtime.base.itc.alg.representative.RepresentativeElectionAlgorithm; +import tools.refinery.viatra.runtime.base.itc.alg.representative.StronglyConnectedComponentAlgorithm; +import tools.refinery.viatra.runtime.base.itc.alg.representative.WeaklyConnectedComponentAlgorithm; +import tools.refinery.viatra.runtime.matchers.context.IPosetComparator; +import tools.refinery.viatra.runtime.matchers.psystem.IExpressionEvaluator; +import tools.refinery.viatra.runtime.matchers.psystem.IRelationEvaluator; +import tools.refinery.viatra.runtime.matchers.psystem.aggregations.IMultisetAggregationOperator; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.rete.aggregation.ColumnAggregatorNode; +import tools.refinery.viatra.runtime.rete.aggregation.CountNode; +import tools.refinery.viatra.runtime.rete.aggregation.IAggregatorNode; +import tools.refinery.viatra.runtime.rete.aggregation.timely.FaithfulParallelTimelyColumnAggregatorNode; +import tools.refinery.viatra.runtime.rete.aggregation.timely.FaithfulSequentialTimelyColumnAggregatorNode; +import tools.refinery.viatra.runtime.rete.aggregation.timely.FirstOnlyParallelTimelyColumnAggregatorNode; +import tools.refinery.viatra.runtime.rete.aggregation.timely.FirstOnlySequentialTimelyColumnAggregatorNode; +import tools.refinery.viatra.runtime.rete.boundary.ExternalInputEnumeratorNode; +import tools.refinery.viatra.runtime.rete.boundary.ExternalInputStatelessFilterNode; +import tools.refinery.viatra.runtime.rete.eval.EvaluatorCore; +import tools.refinery.viatra.runtime.rete.eval.MemorylessEvaluatorNode; +import tools.refinery.viatra.runtime.rete.eval.OutputCachingEvaluatorNode; +import tools.refinery.viatra.runtime.rete.eval.RelationEvaluatorNode; +import tools.refinery.viatra.runtime.rete.index.ExistenceNode; +import tools.refinery.viatra.runtime.rete.index.Indexer; +import tools.refinery.viatra.runtime.rete.index.JoinNode; +import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration; +import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration.AggregatorArchitecture; +import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration.TimelineRepresentation; +import tools.refinery.viatra.runtime.rete.misc.ConstantNode; +import tools.refinery.viatra.runtime.rete.recipes.*; +import tools.refinery.viatra.runtime.rete.single.*; +import tools.refinery.viatra.runtime.rete.traceability.TraceInfo; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Factory for instantiating Rete nodes. The created nodes are not connected to the network yet. + * + * @author Bergmann Gabor + * + */ +class NodeFactory { + Logger logger; + + public NodeFactory(Logger logger) { + super(); + this.logger = logger; + } + + /** + * PRE: parent node must already be created + */ + public Indexer createIndexer(ReteContainer reteContainer, IndexerRecipe recipe, Supplier parentNode, + TraceInfo... traces) { + + if (recipe instanceof ProjectionIndexerRecipe) { + return parentNode.constructIndex(toMask(recipe.getMask()), traces); + // already traced + } else if (recipe instanceof AggregatorIndexerRecipe) { + int indexOfAggregateResult = recipe.getParent().getArity(); + int resultPosition = recipe.getMask().getSourceIndices().lastIndexOf(indexOfAggregateResult); + + IAggregatorNode aggregatorNode = (IAggregatorNode) parentNode; + final Indexer result = (resultPosition == -1) ? aggregatorNode.getAggregatorOuterIndexer() + : aggregatorNode.getAggregatorOuterIdentityIndexer(resultPosition); + + for (TraceInfo traceInfo : traces) + result.assignTraceInfo(traceInfo); + return result; + } else + throw new IllegalArgumentException("Unkown Indexer recipe: " + recipe); + } + + /** + * PRE: recipe is not an indexer recipe. + */ + public Supplier createNode(ReteContainer reteContainer, ReteNodeRecipe recipe, TraceInfo... traces) { + if (recipe instanceof IndexerRecipe) + throw new IllegalArgumentException("Indexers are not created by NodeFactory: " + recipe); + + Supplier result = instantiateNodeDispatch(reteContainer, recipe); + for (TraceInfo traceInfo : traces) + result.assignTraceInfo(traceInfo); + return result; + } + + private Supplier instantiateNodeDispatch(ReteContainer reteContainer, ReteNodeRecipe recipe) { + + // Parentless + + if (recipe instanceof ConstantRecipe) + return instantiateNode(reteContainer, (ConstantRecipe) recipe); + if (recipe instanceof InputRecipe) + return instantiateNode(reteContainer, (InputRecipe) recipe); + + // SingleParentNodeRecipe + + // if (recipe instanceof ProjectionIndexer) + // return instantiateNode((ProjectionIndexer)recipe); + if (recipe instanceof InputFilterRecipe) + return instantiateNode(reteContainer, (InputFilterRecipe) recipe); + if (recipe instanceof InequalityFilterRecipe) + return instantiateNode(reteContainer, (InequalityFilterRecipe) recipe); + if (recipe instanceof EqualityFilterRecipe) + return instantiateNode(reteContainer, (EqualityFilterRecipe) recipe); + if (recipe instanceof TransparentRecipe) + return instantiateNode(reteContainer, (TransparentRecipe) recipe); + if (recipe instanceof TrimmerRecipe) + return instantiateNode(reteContainer, (TrimmerRecipe) recipe); + if (recipe instanceof TransitiveClosureRecipe) + return instantiateNode(reteContainer, (TransitiveClosureRecipe) recipe); + if (recipe instanceof RepresentativeElectionRecipe) + return instantiateNode(reteContainer, (RepresentativeElectionRecipe) recipe); + if (recipe instanceof RelationEvaluationRecipe) + return instantiateNode(reteContainer, (RelationEvaluationRecipe) recipe); + if (recipe instanceof ExpressionEnforcerRecipe) + return instantiateNode(reteContainer, (ExpressionEnforcerRecipe) recipe); + if (recipe instanceof CountAggregatorRecipe) + return instantiateNode(reteContainer, (CountAggregatorRecipe) recipe); + if (recipe instanceof SingleColumnAggregatorRecipe) + return instantiateNode(reteContainer, (SingleColumnAggregatorRecipe) recipe); + if (recipe instanceof DiscriminatorDispatcherRecipe) + return instantiateNode(reteContainer, (DiscriminatorDispatcherRecipe) recipe); + if (recipe instanceof DiscriminatorBucketRecipe) + return instantiateNode(reteContainer, (DiscriminatorBucketRecipe) recipe); + + // MultiParentNodeRecipe + if (recipe instanceof UniquenessEnforcerRecipe) + return instantiateNode(reteContainer, (UniquenessEnforcerRecipe) recipe); + if (recipe instanceof ProductionRecipe) + return instantiateNode(reteContainer, (ProductionRecipe) recipe); + + // BetaNodeRecipe + if (recipe instanceof JoinRecipe) + return instantiateNode(reteContainer, (JoinRecipe) recipe); + if (recipe instanceof SemiJoinRecipe) + return instantiateNode(reteContainer, (SemiJoinRecipe) recipe); + if (recipe instanceof AntiJoinRecipe) + return instantiateNode(reteContainer, (AntiJoinRecipe) recipe); + + // ... else + throw new IllegalArgumentException("Unsupported recipe type: " + recipe); + } + + // INSTANTIATION for recipe types + + private Supplier instantiateNode(ReteContainer reteContainer, InputRecipe recipe) { + return new ExternalInputEnumeratorNode(reteContainer); + } + + private Supplier instantiateNode(ReteContainer reteContainer, InputFilterRecipe recipe) { + return new ExternalInputStatelessFilterNode(reteContainer, toMaskOrNull(recipe.getMask())); + } + + private Supplier instantiateNode(ReteContainer reteContainer, CountAggregatorRecipe recipe) { + return new CountNode(reteContainer); + } + + private Supplier instantiateNode(ReteContainer reteContainer, TransparentRecipe recipe) { + return new TransparentNode(reteContainer); + } + + private Supplier instantiateNode(ReteContainer reteContainer, ExpressionEnforcerRecipe recipe) { + final IExpressionEvaluator evaluator = toIExpressionEvaluator(recipe.getExpression()); + final Map posMapping = toStringIndexMap(recipe.getMappedIndices()); + final int sourceTupleWidth = recipe.getParent().getArity(); + EvaluatorCore core = null; + if (recipe instanceof CheckRecipe) { + core = new EvaluatorCore.PredicateEvaluatorCore(logger, evaluator, posMapping, sourceTupleWidth); + } else if (recipe instanceof EvalRecipe) { + final boolean isUnwinding = ((EvalRecipe) recipe).isUnwinding(); + core = new EvaluatorCore.FunctionEvaluatorCore(logger, evaluator, posMapping, sourceTupleWidth, isUnwinding); + } else { + throw new IllegalArgumentException("Unhandled expression enforcer recipe: " + recipe.getClass() + "!"); + } + if (recipe.isCacheOutput()) { + return new OutputCachingEvaluatorNode(reteContainer, core); + } else { + return new MemorylessEvaluatorNode(reteContainer, core); + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private Supplier instantiateNode(ReteContainer reteContainer, SingleColumnAggregatorRecipe recipe) { + final IMultisetAggregationOperator operator = recipe.getMultisetAggregationOperator(); + TupleMask coreMask = null; + if (recipe.getOptionalMonotonicityInfo() != null) { + coreMask = toMask(recipe.getOptionalMonotonicityInfo().getCoreMask()); + } else { + coreMask = toMask(recipe.getGroupByMask()); + } + + if (reteContainer.isTimelyEvaluation()) { + final TimelyConfiguration timelyConfiguration = reteContainer.getTimelyConfiguration(); + final AggregatorArchitecture aggregatorArchitecture = timelyConfiguration.getAggregatorArchitecture(); + final TimelineRepresentation timelineRepresentation = timelyConfiguration.getTimelineRepresentation(); + + TupleMask posetMask = null; + + if (recipe.getOptionalMonotonicityInfo() != null) { + posetMask = toMask(recipe.getOptionalMonotonicityInfo().getPosetMask()); + } else { + final int aggregatedColumn = recipe.getAggregableIndex(); + posetMask = TupleMask.selectSingle(aggregatedColumn, coreMask.sourceWidth); + } + + if (timelineRepresentation == TimelineRepresentation.FIRST_ONLY + && aggregatorArchitecture == AggregatorArchitecture.SEQUENTIAL) { + return new FirstOnlySequentialTimelyColumnAggregatorNode(reteContainer, operator, coreMask, posetMask); + } else if (timelineRepresentation == TimelineRepresentation.FIRST_ONLY + && aggregatorArchitecture == AggregatorArchitecture.PARALLEL) { + return new FirstOnlyParallelTimelyColumnAggregatorNode(reteContainer, operator, coreMask, posetMask); + } else if (timelineRepresentation == TimelineRepresentation.FAITHFUL + && aggregatorArchitecture == AggregatorArchitecture.SEQUENTIAL) { + return new FaithfulSequentialTimelyColumnAggregatorNode(reteContainer, operator, coreMask, posetMask); + } else if (timelineRepresentation == TimelineRepresentation.FAITHFUL + && aggregatorArchitecture == AggregatorArchitecture.PARALLEL) { + return new FaithfulParallelTimelyColumnAggregatorNode(reteContainer, operator, coreMask, posetMask); + } else { + throw new IllegalArgumentException("Unsupported timely configuration!"); + } + } else if (recipe.isDeleteRederiveEvaluation() && recipe.getOptionalMonotonicityInfo() != null) { + final TupleMask posetMask = toMask(recipe.getOptionalMonotonicityInfo().getPosetMask()); + final IPosetComparator posetComparator = (IPosetComparator) recipe.getOptionalMonotonicityInfo() + .getPosetComparator(); + return new ColumnAggregatorNode(reteContainer, operator, recipe.isDeleteRederiveEvaluation(), coreMask, + posetMask, posetComparator); + } else { + final int aggregatedColumn = recipe.getAggregableIndex(); + return new ColumnAggregatorNode(reteContainer, operator, coreMask, aggregatedColumn); + } + } + + private Supplier instantiateNode(ReteContainer reteContainer, TransitiveClosureRecipe recipe) { + return new TransitiveClosureNode(reteContainer); + } + + private Supplier instantiateNode(ReteContainer reteContainer, RepresentativeElectionRecipe recipe) { + RepresentativeElectionAlgorithm.Factory algorithmFactory = switch (recipe.getConnectivity()) { + case STRONG -> StronglyConnectedComponentAlgorithm::new; + case WEAK -> WeaklyConnectedComponentAlgorithm::new; + }; + return new RepresentativeElectionNode(reteContainer, algorithmFactory); + } + + private Supplier instantiateNode(ReteContainer reteContainer, RelationEvaluationRecipe recipe) { + return new RelationEvaluatorNode(reteContainer, toIRelationEvaluator(recipe.getEvaluator())); + } + + private Supplier instantiateNode(ReteContainer reteContainer, ProductionRecipe recipe) { + if (reteContainer.isTimelyEvaluation()) { + return new TimelyProductionNode(reteContainer, toStringIndexMap(recipe.getMappedIndices())); + } else if (recipe.isDeleteRederiveEvaluation() && recipe.getOptionalMonotonicityInfo() != null) { + TupleMask coreMask = toMask(recipe.getOptionalMonotonicityInfo().getCoreMask()); + TupleMask posetMask = toMask(recipe.getOptionalMonotonicityInfo().getPosetMask()); + IPosetComparator posetComparator = (IPosetComparator) recipe.getOptionalMonotonicityInfo() + .getPosetComparator(); + return new DefaultProductionNode(reteContainer, toStringIndexMap(recipe.getMappedIndices()), + recipe.isDeleteRederiveEvaluation(), coreMask, posetMask, posetComparator); + } else { + return new DefaultProductionNode(reteContainer, toStringIndexMap(recipe.getMappedIndices()), + recipe.isDeleteRederiveEvaluation()); + } + } + + private Supplier instantiateNode(ReteContainer reteContainer, UniquenessEnforcerRecipe recipe) { + if (reteContainer.isTimelyEvaluation()) { + return new TimelyUniquenessEnforcerNode(reteContainer, recipe.getArity()); + } else if (recipe.isDeleteRederiveEvaluation() && recipe.getOptionalMonotonicityInfo() != null) { + TupleMask coreMask = toMask(recipe.getOptionalMonotonicityInfo().getCoreMask()); + TupleMask posetMask = toMask(recipe.getOptionalMonotonicityInfo().getPosetMask()); + IPosetComparator posetComparator = (IPosetComparator) recipe.getOptionalMonotonicityInfo() + .getPosetComparator(); + return new UniquenessEnforcerNode(reteContainer, recipe.getArity(), recipe.isDeleteRederiveEvaluation(), + coreMask, posetMask, posetComparator); + } else { + return new UniquenessEnforcerNode(reteContainer, recipe.getArity(), recipe.isDeleteRederiveEvaluation()); + } + } + + private Supplier instantiateNode(ReteContainer reteContainer, ConstantRecipe recipe) { + final List constantValues = recipe.getConstantValues(); + final Object[] constantArray = constantValues.toArray(new Object[constantValues.size()]); + return new ConstantNode(reteContainer, Tuples.flatTupleOf(constantArray)); + } + + private Supplier instantiateNode(ReteContainer reteContainer, DiscriminatorBucketRecipe recipe) { + return new DiscriminatorBucketNode(reteContainer, recipe.getBucketKey()); + } + + private Supplier instantiateNode(ReteContainer reteContainer, DiscriminatorDispatcherRecipe recipe) { + return new DiscriminatorDispatcherNode(reteContainer, recipe.getDiscriminationColumnIndex()); + } + + private Supplier instantiateNode(ReteContainer reteContainer, TrimmerRecipe recipe) { + return new TrimmerNode(reteContainer, toMask(recipe.getMask())); + } + + private Supplier instantiateNode(ReteContainer reteContainer, InequalityFilterRecipe recipe) { + Tunnel result = new InequalityFilterNode(reteContainer, recipe.getSubject(), + TupleMask.fromSelectedIndices(recipe.getParent().getArity(), recipe.getInequals())); + return result; + } + + private Supplier instantiateNode(ReteContainer reteContainer, EqualityFilterRecipe recipe) { + final int[] equalIndices = TupleMask.integersToIntArray(recipe.getIndices()); + return new EqualityFilterNode(reteContainer, equalIndices); + } + + private Supplier instantiateNode(ReteContainer reteContainer, AntiJoinRecipe recipe) { + return new ExistenceNode(reteContainer, true); + } + + private Supplier instantiateNode(ReteContainer reteContainer, SemiJoinRecipe recipe) { + return new ExistenceNode(reteContainer, false); + } + + private Supplier instantiateNode(ReteContainer reteContainer, JoinRecipe recipe) { + return new JoinNode(reteContainer, toMask(recipe.getRightParentComplementaryMask())); + } + + // HELPERS + + private IExpressionEvaluator toIExpressionEvaluator(ExpressionDefinition expressionDefinition) { + final Object evaluator = expressionDefinition.getEvaluator(); + if (evaluator instanceof IExpressionEvaluator) { + return (IExpressionEvaluator) evaluator; + } + throw new IllegalArgumentException("No runtime support for expression evaluator: " + evaluator); + } + + private IRelationEvaluator toIRelationEvaluator(ExpressionDefinition expressionDefinition) { + final Object evaluator = expressionDefinition.getEvaluator(); + if (evaluator instanceof IRelationEvaluator) { + return (IRelationEvaluator) evaluator; + } + throw new IllegalArgumentException("No runtime support for relation evaluator: " + evaluator); + } + + private Map toStringIndexMap(final EMap mappedIndices) { + final HashMap result = new HashMap(); + for (java.util.Map.Entry entry : mappedIndices) { + result.put(entry.getKey(), entry.getValue()); + } + return result; + } + + /** Mask can be null */ + private TupleMask toMaskOrNull(Mask mask) { + if (mask == null) + return null; + else + return toMask(mask); + } + + /** Mask is non-null. */ + private TupleMask toMask(Mask mask) { + return TupleMask.fromSelectedIndices(mask.getSourceArity(), mask.getSourceIndices()); + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/NodeProvisioner.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/NodeProvisioner.java new file mode 100644 index 00000000..9121fc44 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/NodeProvisioner.java @@ -0,0 +1,346 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.network; + +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.rete.boundary.InputConnector; +import tools.refinery.viatra.runtime.rete.construction.plancompiler.CompilerHelper; +import tools.refinery.viatra.runtime.rete.index.Indexer; +import tools.refinery.viatra.runtime.rete.index.OnetimeIndexer; +import tools.refinery.viatra.runtime.rete.index.ProjectionIndexer; +import tools.refinery.viatra.runtime.rete.network.delayed.DelayedConnectCommand; +import tools.refinery.viatra.runtime.rete.recipes.*; +import tools.refinery.viatra.runtime.rete.recipes.helper.RecipeRecognizer; +import tools.refinery.viatra.runtime.rete.recipes.helper.RecipesHelper; +import tools.refinery.viatra.runtime.rete.remote.Address; +import tools.refinery.viatra.runtime.rete.remote.RemoteReceiver; +import tools.refinery.viatra.runtime.rete.remote.RemoteSupplier; +import tools.refinery.viatra.runtime.rete.traceability.ActiveNodeConflictTrace; +import tools.refinery.viatra.runtime.rete.traceability.RecipeTraceInfo; +import tools.refinery.viatra.runtime.rete.traceability.UserRequestTrace; +import tools.refinery.viatra.runtime.rete.util.Options; + +import java.util.Map; +import java.util.Set; + +/** + * Stores the internal parts of a rete network. Nodes are stored according to type and parameters. + * + * @author Gabor Bergmann + */ +public class NodeProvisioner { + + // boolean activeStorage = true; + + ReteContainer reteContainer; + NodeFactory nodeFactory; + ConnectionFactory connectionFactory; + InputConnector inputConnector; + IQueryRuntimeContext runtimeContext; + + // TODO as recipe? + Map remoteReceivers = CollectionsFactory.createMap(); + Map, RemoteSupplier> remoteSuppliers = CollectionsFactory.createMap(); + + private RecipeRecognizer recognizer; + + /** + * PRE: NodeFactory, ConnectionFactory must exist + * + * @param reteContainer + * the ReteNet whose interior is to be mapped. + */ + public NodeProvisioner(ReteContainer reteContainer) { + super(); + this.reteContainer = reteContainer; + this.nodeFactory = reteContainer.getNodeFactory(); + this.connectionFactory = reteContainer.getConnectionFactory(); + this.inputConnector = reteContainer.getInputConnectionFactory(); + runtimeContext = reteContainer.getNetwork().getEngine().getRuntimeContext(); + recognizer = new RecipeRecognizer(runtimeContext); + } + + public synchronized Address getOrCreateNodeByRecipe(RecipeTraceInfo recipeTrace) { + ReteNodeRecipe recipe = recipeTrace.getRecipe(); + Address result = getNodesByRecipe().get(recipe); + if (result != null) { + // NODE ALREADY CONSTRUCTED FOR RECIPE, only needs to add trace + if (getRecipeTraces().add(recipeTrace)) + result.getNodeCache().assignTraceInfo(recipeTrace); + } else { + // No node for this recipe object - but equivalent recipes still + // reusable + ReteNodeRecipe canonicalRecipe = recognizer.canonicalizeRecipe(recipe); + if (canonicalRecipe != recipe) { + // FOUND EQUIVALENT RECIPE + result = getNodesByRecipe().get(canonicalRecipe); + if (result != null) { + // NODE ALREADY CONSTRUCTED FOR EQUIVALENT RECIPE + recipeTrace.shadowWithEquivalentRecipe(canonicalRecipe); + getNodesByRecipe().put(recipe, result); + if (getRecipeTraces().add(recipeTrace)) + result.getNodeCache().assignTraceInfo(recipeTrace); + // Bug 491922: ensure that recipe shadowing propagates to + // parent traces + // note that if equivalentRecipes() becomes more + // sophisticated + // and considers recipes with different parents, this might + // have to be changed + ensureParents(recipeTrace); + } else { + // CONSTRUCTION IN PROGRESS FOR EQUIVALENT RECIPE + if (recipe instanceof IndexerRecipe) { + // this is allowed for indexers; + // go on with the construction, as the same indexer node + // will be obtained anyways + } else { + throw new IllegalStateException( + "This should not happen: " + "non-indexer nodes are are supposed to be constructed " + + "as soon as they are designated as canonical recipes"); + } + } + } + if (result == null) { + // MUST INSTANTIATE NEW NODE FOR RECIPE + final Node freshNode = instantiateNodeForRecipe(recipeTrace, recipe); + result = reteContainer.makeAddress(freshNode); + } + } + return result; + } + + private Set getRecipeTraces() { + return reteContainer.network.recipeTraces; + } + + private Node instantiateNodeForRecipe(RecipeTraceInfo recipeTrace, final ReteNodeRecipe recipe) { + this.getRecipeTraces().add(recipeTrace); + if (recipe instanceof IndexerRecipe) { + + // INSTANTIATE AND HOOK UP + // (cannot delay hooking up, because parent determines indexer + // implementation) + ensureParents(recipeTrace); + final ReteNodeRecipe parentRecipe = recipeTrace.getParentRecipeTraces().iterator().next().getRecipe(); + final Indexer result = nodeFactory.createIndexer(reteContainer, (IndexerRecipe) recipe, + asSupplier( + (Address) reteContainer.network.getExistingNodeByRecipe(parentRecipe)), + recipeTrace); + + // REMEMBER + if (Options.nodeSharingOption != Options.NodeSharingOption.NEVER) { + getNodesByRecipe().put(recipe, reteContainer.makeAddress(result)); + } + + return result; + } else { + + // INSTANTIATE + Node result = nodeFactory.createNode(reteContainer, recipe, recipeTrace); + + // REMEMBER + if (Options.nodeSharingOption == Options.NodeSharingOption.ALL) { + getNodesByRecipe().put(recipe, reteContainer.makeAddress(result)); + } + + // HOOK UP + // (recursion-tolerant due to this delayed order of initialization) + if (recipe instanceof InputRecipe) { + inputConnector.connectInput((InputRecipe) recipe, result); + } else { + if (recipe instanceof InputFilterRecipe) + inputConnector.connectInputFilter((InputFilterRecipe) recipe, result); + ensureParents(recipeTrace); + connectionFactory.connectToParents(recipeTrace, result); + } + return result; + } + } + + private Map> getNodesByRecipe() { + return reteContainer.network.nodesByRecipe; + } + + private void ensureParents(RecipeTraceInfo recipeTrace) { + for (RecipeTraceInfo parentTrace : recipeTrace.getParentRecipeTraces()) { + getOrCreateNodeByRecipe(parentTrace); + } + } + + //// Remoting - TODO eliminate? + + synchronized RemoteReceiver accessRemoteReceiver(Address address) { + throw new UnsupportedOperationException("Multi-container Rete not supported yet"); + // if (!reteContainer.isLocal(address)) + // return + // address.getContainer().getProvisioner().accessRemoteReceiver(address); + // Supplier localSupplier = reteContainer.resolveLocal(address); + // RemoteReceiver result = remoteReceivers.get(localSupplier); + // if (result == null) { + // result = new RemoteReceiver(reteContainer); + // reteContainer.connect(localSupplier, result); // stateless node, no + // // synch required + // + // if (Options.nodeSharingOption != Options.NodeSharingOption.NEVER) + // remoteReceivers.put(localSupplier, result); + // } + // return result; + } + + /** + * @pre: address is NOT local + */ + synchronized RemoteSupplier accessRemoteSupplier(Address address) { + throw new UnsupportedOperationException("Multi-container Rete not supported yet"); + // RemoteSupplier result = remoteSuppliers.get(address); + // if (result == null) { + // result = new RemoteSupplier(reteContainer, + // address.getContainer().getProvisioner() + // .accessRemoteReceiver(address)); + // // network.connectAndSynchronize(supplier, result); + // + // if (Options.nodeSharingOption != Options.NodeSharingOption.NEVER) + // remoteSuppliers.put(address, result); + // } + // return result; + } + + /** + * The powerful method for accessing any (supplier) Address as a local supplier. + */ + public Supplier asSupplier(Address address) { + if (!reteContainer.isLocal(address)) + return accessRemoteSupplier(address); + else + return reteContainer.resolveLocal(address); + } + + /** the composite key tuple is formed as (RecipeTraceInfo, TupleMask) */ + private Map projectionIndexerUserRequests = CollectionsFactory.createMap(); + + // local version + // TODO remove? + public synchronized ProjectionIndexer accessProjectionIndexer(RecipeTraceInfo productionTrace, TupleMask mask) { + Tuple tableKey = Tuples.staticArityFlatTupleOf(productionTrace, mask); + UserRequestTrace indexerTrace = projectionIndexerUserRequests.computeIfAbsent(tableKey, k -> { + final ProjectionIndexerRecipe projectionIndexerRecipe = projectionIndexerRecipe( + productionTrace, mask); + return new UserRequestTrace(projectionIndexerRecipe, productionTrace); + }); + final Address address = getOrCreateNodeByRecipe(indexerTrace); + return (ProjectionIndexer) reteContainer.resolveLocal(address); + } + + // local version + public synchronized ProjectionIndexer accessProjectionIndexerOnetime(RecipeTraceInfo supplierTrace, + TupleMask mask) { + if (Options.nodeSharingOption != Options.NodeSharingOption.NEVER) + return accessProjectionIndexer(supplierTrace, mask); + + final Address supplierAddress = getOrCreateNodeByRecipe(supplierTrace); + Supplier supplier = (Supplier) reteContainer.resolveLocal(supplierAddress); + + OnetimeIndexer result = new OnetimeIndexer(reteContainer, mask); + reteContainer.getDelayedCommandQueue().add(new DelayedConnectCommand(supplier, result, reteContainer)); + + return result; + } + + // local, read-only version + public synchronized ProjectionIndexer peekProjectionIndexer(RecipeTraceInfo supplierTrace, TupleMask mask) { + final Address address = getNodesByRecipe().get(projectionIndexerRecipe(supplierTrace, mask)); + return address == null ? null : (ProjectionIndexer) reteContainer.resolveLocal(address); + } + + private ProjectionIndexerRecipe projectionIndexerRecipe( + RecipeTraceInfo parentTrace, TupleMask mask) { + final ReteNodeRecipe parentRecipe = parentTrace.getRecipe(); + Tuple tableKey = Tuples.staticArityFlatTupleOf(parentRecipe, mask); + ProjectionIndexerRecipe projectionIndexerRecipe = resultSeedRecipes.computeIfAbsent(tableKey, k -> + RecipesHelper.projectionIndexerRecipe(parentRecipe, CompilerHelper.toRecipeMask(mask)) + ); + return projectionIndexerRecipe; + } + + /** the composite key tuple is formed as (ReteNodeRecipe, TupleMask) */ + private Map resultSeedRecipes = CollectionsFactory.createMap(); + + // public synchronized Address + // accessValueBinderFilterNode( + // Address supplierAddress, int bindingIndex, Object + // bindingValue) { + // Supplier supplier = asSupplier(supplierAddress); + // Object[] paramsArray = { supplier.getNodeId(), bindingIndex, bindingValue + // }; + // Tuple params = new FlatTuple(paramsArray); + // ValueBinderFilterNode result = valueBinderFilters.get(params); + // if (result == null) { + // result = new ValueBinderFilterNode(reteContainer, bindingIndex, + // bindingValue); + // reteContainer.connect(supplier, result); // stateless node, no synch + // // required + // + // if (Options.nodeSharingOption == Options.NodeSharingOption.ALL) + // valueBinderFilters.put(params, result); + // } + // return reteContainer.makeAddress(result); + // } + + /** + * Returns a copy of the given indexer that is an active node by itself (created if does not exist). (Convention: + * attached with same mask to a transparent node that is attached to parent node.) Node is created if it does not + * exist yet. + * + * @return an identical but active indexer + */ + // TODO rethink traceability + RecipeTraceInfo accessActiveIndexer(RecipeTraceInfo inactiveIndexerRecipeTrace) { + final RecipeTraceInfo parentRecipeTrace = inactiveIndexerRecipeTrace.getParentRecipeTraces().iterator().next(); + final ProjectionIndexerRecipe inactiveIndexerRecipe = (ProjectionIndexerRecipe) inactiveIndexerRecipeTrace + .getRecipe(); + + final TransparentRecipe transparentRecipe = RecipesFactory.eINSTANCE.createTransparentRecipe(); + transparentRecipe.setParent(parentRecipeTrace.getRecipe()); + final ActiveNodeConflictTrace transparentRecipeTrace = new ActiveNodeConflictTrace(transparentRecipe, + parentRecipeTrace, inactiveIndexerRecipeTrace); + + final ProjectionIndexerRecipe activeIndexerRecipe = RecipesFactory.eINSTANCE + .createProjectionIndexerRecipe(); + activeIndexerRecipe.setParent(transparentRecipe); + activeIndexerRecipe.setMask(inactiveIndexerRecipe.getMask()); + final ActiveNodeConflictTrace activeIndexerRecipeTrace = new ActiveNodeConflictTrace(activeIndexerRecipe, + transparentRecipeTrace, inactiveIndexerRecipeTrace); + + return activeIndexerRecipeTrace; + } + + // /** + // * @param parent + // * @return + // */ + // private TransparentNode accessTransparentNodeInternal(Supplier parent) { + // nodeFactory. + // return null; + // } + + // public synchronized void registerSpecializedProjectionIndexer(Node node, + // ProjectionIndexer indexer) { + // if (Options.nodeSharingOption != Options.NodeSharingOption.NEVER) { + // Object[] paramsArray = { node.getNodeId(), indexer.getMask() }; + // Tuple params = new FlatTuple(paramsArray); + // projectionIndexers.put(params, indexer); + // } + // } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/PosetAwareReceiver.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/PosetAwareReceiver.java new file mode 100644 index 00000000..1eaa18e7 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/PosetAwareReceiver.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network; + +import tools.refinery.viatra.runtime.matchers.context.IPosetComparator; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.Direction; + +/** + * @author Tamas Szabo + * @since 2.0 + */ +public interface PosetAwareReceiver extends Receiver { + + public TupleMask getCoreMask(); + + public TupleMask getPosetMask(); + + public IPosetComparator getPosetComparator(); + + /** + * Updates the receiver with a newly found or lost partial matching also providing information + * whether the update is a monotone change or not. + * + * @param direction the direction of the update + * @param update the update tuple + * @param monotone true if the update is monotone, false otherwise + * @since 2.4 + */ + public void updateWithPosetInfo(Direction direction, Tuple update, boolean monotone); + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ProductionNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ProductionNode.java new file mode 100644 index 00000000..211194c0 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ProductionNode.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.network; + +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; + +/** + * Interface intended for nodes containing complete matches. + * + * @author Gabor Bergmann + */ +public interface ProductionNode extends Tunnel, Iterable { + + /** + * @return the position mapping of this particular pattern that maps members of the tuple type to their positions + */ + Map getPosMapping(); + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Receiver.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Receiver.java new file mode 100644 index 00000000..3dc9aad7 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Receiver.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.network; + +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; + +/** + * ALL METHODS: FOR INTERNAL USE ONLY; ONLY INVOKE FROM {@link ReteContainer} + * + * @author Gabor Bergmann + * @noimplement This interface is not intended to be implemented by external clients. + */ +public interface Receiver extends Node { + + /** + * Updates the receiver with a newly found or lost partial matching. + * + * @since 2.4 + */ + public void update(final Direction direction, final Tuple updateElement, final Timestamp timestamp); + + /** + * Updates the receiver in batch style with a collection of updates. The input collection consists of pairs in the + * form (t, c) where t is an update tuple and c is the count. The count can also be negative, and it specifies how + * many times the tuple t gets deleted or inserted. The default implementation of this method simply calls + * {@link #update(Direction, Tuple, Timestamp)} individually for all updates. + * + * @since 2.8 + */ + public default void batchUpdate(final Collection> updates, final Timestamp timestamp) { + for (final Entry entry : updates) { + int count = entry.getValue(); + + Direction direction; + if (count < 0) { + direction = Direction.DELETE; + count = -count; + } else { + direction = Direction.INSERT; + } + + for (int i = 0; i < count; i++) { + update(direction, entry.getKey(), timestamp); + } + } + } + + /** + * Returns the {@link Mailbox} of this receiver. + * + * @return the mailbox + * @since 2.0 + */ + public Mailbox getMailbox(); + + /** + * appends a parent that will continuously send insert and revoke updates to this supplier + */ + void appendParent(final Supplier supplier); + + /** + * removes a parent + */ + void removeParent(final Supplier supplier); + + /** + * access active parent + */ + Collection getParents(); + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/RederivableNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/RederivableNode.java new file mode 100644 index 00000000..cae78d37 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/RederivableNode.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network; + +/** + * A rederivable node can potentially re-derive tuples after the Rete network has finished the delivery of messages. + * + * @author Tamas Szabo + * @since 1.6 + */ +public interface RederivableNode extends Node, IGroupable { + + /** + * The method is called by the {@link ReteContainer} to re-derive tuples after the normal messages have been + * delivered and consumed. The re-derivation process may trigger the creation and delivery of further messages + * and further re-derivation rounds. + */ + public void rederiveOne(); + + /** + * Returns true if this node actually runs in DRed mode (not necessarily). + * + * @return true if the node is operating in DRed mode + * @since 2.0 + */ + public boolean isInDRedMode(); + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ReinitializedNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ReinitializedNode.java new file mode 100644 index 00000000..09bff29e --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ReinitializedNode.java @@ -0,0 +1,14 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.viatra.runtime.rete.network; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; + +import java.util.Collection; + +public interface ReinitializedNode { + void reinitializeWith(Collection tuples); +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ReteContainer.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ReteContainer.java new file mode 100644 index 00000000..16e290fd --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/ReteContainer.java @@ -0,0 +1,729 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.network; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Deque; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + +import org.apache.log4j.Logger; +import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Clearable; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.boundary.InputConnector; +import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.communication.timeless.TimelessCommunicationTracker; +import tools.refinery.viatra.runtime.rete.network.communication.timely.TimelyCommunicationTracker; +import tools.refinery.viatra.runtime.rete.network.delayed.DelayedCommand; +import tools.refinery.viatra.runtime.rete.network.delayed.DelayedConnectCommand; +import tools.refinery.viatra.runtime.rete.network.delayed.DelayedDisconnectCommand; +import tools.refinery.viatra.runtime.rete.remote.Address; +import tools.refinery.viatra.runtime.rete.single.SingleInputNode; +import tools.refinery.viatra.runtime.rete.single.TrimmerNode; +import tools.refinery.viatra.runtime.rete.util.Options; + +/** + * @author Gabor Bergmann + * + * Mutexes: externalMessageLock - enlisting messages into and retrieving from the external message queue + * @since 2.2 + */ +public final class ReteContainer { + + protected Thread consumerThread = null; + protected boolean killed = false; + + protected Network network; + + protected LinkedList clearables; + protected Map nodesById; + protected long nextId = 0; + + protected ConnectionFactory connectionFactory; + protected NodeProvisioner nodeProvisioner; + + protected Deque internalMessageQueue = new ArrayDeque(); + protected/* volatile */Deque externalMessageQueue = new ArrayDeque(); + protected Object externalMessageLock = new Object(); + protected Long clock = 1L; // even: steady state, odd: active queue; access + // ONLY with messageQueue locked! + protected Map terminationCriteria = null; + protected final Logger logger; + protected final CommunicationTracker tracker; + + protected final IQueryBackendContext backendContext; + + protected Set delayedCommandQueue; + protected Set delayedCommandBuffer; + protected boolean executingDelayedCommands; + + protected final TimelyConfiguration timelyConfiguration; + + /** + * @param threaded + * false if operating in a single-threaded environment + */ + public ReteContainer(Network network, boolean threaded) { + super(); + this.network = network; + this.backendContext = network.getEngine().getBackendContext(); + this.timelyConfiguration = network.getEngine().getTimelyConfiguration(); + + this.delayedCommandQueue = new LinkedHashSet(); + this.delayedCommandBuffer = new LinkedHashSet(); + this.executingDelayedCommands = false; + + if (this.isTimelyEvaluation()) { + this.tracker = new TimelyCommunicationTracker(this.getTimelyConfiguration()); + } else { + this.tracker = new TimelessCommunicationTracker(); + } + + this.nodesById = CollectionsFactory.createMap(); + this.clearables = new LinkedList(); + this.logger = network.getEngine().getLogger(); + + this.connectionFactory = new ConnectionFactory(this); + this.nodeProvisioner = new NodeProvisioner(this); + + if (threaded) { + this.terminationCriteria = CollectionsFactory.createMap(); + this.consumerThread = new Thread("Rete thread of " + ReteContainer.super.toString()) { + @Override + public void run() { + messageConsumptionCycle(); + } + }; + this.consumerThread.start(); + } + } + + /** + * @since 2.4 + */ + public boolean isTimelyEvaluation() { + return this.timelyConfiguration != null; + } + + /** + * @since 2.4 + */ + public TimelyConfiguration getTimelyConfiguration() { + return this.timelyConfiguration; + } + + /** + * @since 1.6 + * @return the communication graph of the nodes, incl. message scheduling + */ + public CommunicationTracker getCommunicationTracker() { + return tracker; + } + + /** + * Stops this container. To be called by Network.kill() + */ + public void kill() { + killed = true; + if (consumerThread != null) + consumerThread.interrupt(); + } + + /** + * Establishes connection between a supplier and a receiver node, regardless which container they are in. Assumption + * is that this container is the home of the receiver, but it is not strictly necessary. + * + * @param synchronise + * indicates whether the receiver should be synchronised to the current contents of the supplier + */ + public void connectRemoteNodes(Address supplier, Address receiver, + boolean synchronise) { + if (!isLocal(receiver)) + receiver.getContainer().connectRemoteNodes(supplier, receiver, synchronise); + else { + Receiver child = resolveLocal(receiver); + connectRemoteSupplier(supplier, child, synchronise); + } + } + + /** + * Severs connection between a supplier and a receiver node, regardless which container they are in. Assumption is + * that this container is the home of the receiver, but it is not strictly necessary. + * + * @param desynchronise + * indicates whether the current contents of the supplier should be subtracted from the receiver + */ + public void disconnectRemoteNodes(Address supplier, Address receiver, + boolean desynchronise) { + if (!isLocal(receiver)) + receiver.getContainer().disconnectRemoteNodes(supplier, receiver, desynchronise); + else { + Receiver child = resolveLocal(receiver); + disconnectRemoteSupplier(supplier, child, desynchronise); + } + } + + /** + * Establishes connection between a remote supplier and a local receiver node. + * + * @param synchronise + * indicates whether the receiver should be synchronised to the current contents of the supplier + */ + public void connectRemoteSupplier(Address supplier, Receiver receiver, boolean synchronise) { + Supplier parent = nodeProvisioner.asSupplier(supplier); + if (synchronise) + connectAndSynchronize(parent, receiver); + else + connect(parent, receiver); + } + + /** + * Severs connection between a remote supplier and a local receiver node. + * + * @param desynchronise + * indicates whether the current contents of the supplier should be subtracted from the receiver + */ + public void disconnectRemoteSupplier(Address supplier, Receiver receiver, + boolean desynchronise) { + Supplier parent = nodeProvisioner.asSupplier(supplier); + if (desynchronise) + disconnectAndDesynchronize(parent, receiver); + else + disconnect(parent, receiver); + } + + /** + * Connects a receiver to a supplier + */ + public void connect(Supplier supplier, Receiver receiver) { + supplier.appendChild(receiver); + receiver.appendParent(supplier); + tracker.registerDependency(supplier, receiver); + } + + /** + * Disconnects a receiver from a supplier + */ + public void disconnect(Supplier supplier, Receiver receiver) { + supplier.removeChild(receiver); + receiver.removeParent(supplier); + tracker.unregisterDependency(supplier, receiver); + } + + /** + * @since 2.3 + */ + public boolean isExecutingDelayedCommands() { + return this.executingDelayedCommands; + } + + /** + * @since 2.3 + */ + public Set getDelayedCommandQueue() { + if (this.executingDelayedCommands) { + return this.delayedCommandBuffer; + } else { + return this.delayedCommandQueue; + } + } + + /** + * Connects a receiver to a remote supplier, and synchronizes it to the current contents of the supplier + */ + public void connectAndSynchronize(Supplier supplier, Receiver receiver) { + supplier.appendChild(receiver); + receiver.appendParent(supplier); + tracker.registerDependency(supplier, receiver); + getDelayedCommandQueue().add(new DelayedConnectCommand(supplier, receiver, this)); + } + + /** + * Disconnects a receiver from a supplier + */ + public void disconnectAndDesynchronize(Supplier supplier, Receiver receiver) { + final boolean wasInSameSCC = this.isTimelyEvaluation() && this.tracker.areInSameGroup(supplier, receiver); + supplier.removeChild(receiver); + receiver.removeParent(supplier); + tracker.unregisterDependency(supplier, receiver); + getDelayedCommandQueue().add(new DelayedDisconnectCommand(supplier, receiver, this, wasInSameSCC)); + } + + /** + * @since 2.3 + */ + public void executeDelayedCommands() { + if (!this.delayedCommandQueue.isEmpty()) { + flushUpdates(); + this.executingDelayedCommands = true; + for (final DelayedCommand command : this.delayedCommandQueue) { + command.run(); + } + this.delayedCommandQueue = this.delayedCommandBuffer; + this.delayedCommandBuffer = new LinkedHashSet(); + flushUpdates(); + this.executingDelayedCommands = false; + } + } + + /** + * Sends an update message to the receiver node, indicating a newly found or lost partial matching. The receiver is + * indicated by the Address. Designed to be called by the Network, DO NOT use in any other way. @pre: + * address.container == this, e.g. address MUST be local + * + * @return the value of the container's clock at the time when the message was accepted into the local message queue + */ + long sendUpdateToLocalAddress(Address address, Direction direction, Tuple updateElement) { + long timestamp; + Receiver receiver = resolveLocal(address); + UpdateMessage message = new UpdateMessage(receiver, direction, updateElement); + synchronized (externalMessageLock) { + externalMessageQueue.add(message); + timestamp = clock; + externalMessageLock.notifyAll(); + } + + return timestamp; + + } + + /** + * Sends multiple update messages atomically to the receiver node, indicating a newly found or lost partial + * matching. The receiver is indicated by the Address. Designed to be called by the Network, DO NOT use in any other + * way. @pre: address.container == this, e.g. address MUST be local @pre: updateElements is nonempty! + * + * @return the value of the container's clock at the time when the message was accepted into the local message queue + */ + long sendUpdatesToLocalAddress(Address address, Direction direction, + Collection updateElements) { + + long timestamp; + Receiver receiver = resolveLocal(address); + // UpdateMessage message = new UpdateMessage(receiver, direction, + // updateElement); + synchronized (externalMessageLock) { + for (Tuple ps : updateElements) + externalMessageQueue.add(new UpdateMessage(receiver, direction, ps)); + // messageQueue.add(new UpdateMessage(resolveLocal(address), + // direction, updateElement)); + // this.sendUpdateInternal(resolveLocal(address), direction, + // updateElement); + timestamp = clock; + externalMessageLock.notifyAll(); + } + + return timestamp; + } + + /** + * Sends an update message to the receiver node, indicating a newly found or lost partial matching. The receiver is + * indicated by the Address. Designed to be called by the Network in single-threaded operation, DO NOT use in any + * other way. + */ + void sendUpdateToLocalAddressSingleThreaded(Address address, Direction direction, + Tuple updateElement) { + Receiver receiver = resolveLocal(address); + UpdateMessage message = new UpdateMessage(receiver, direction, updateElement); + internalMessageQueue.add(message); + } + + /** + * Sends multiple update messages to the receiver node, indicating a newly found or lost partial matching. The + * receiver is indicated by the Address. Designed to be called by the Network in single-threaded operation, DO NOT + * use in any other way. + * + * @pre: address.container == this, e.g. address MUST be local + */ + void sendUpdatesToLocalAddressSingleThreaded(Address address, Direction direction, + Collection updateElements) { + Receiver receiver = resolveLocal(address); + for (Tuple ps : updateElements) + internalMessageQueue.add(new UpdateMessage(receiver, direction, ps)); + } + + /** + * Sends an update message to a node in a different container. The receiver is indicated by the Address. Designed to + * be called by RemoteReceivers, DO NOT use in any other way. + * + * @since 2.4 + */ + public void sendUpdateToRemoteAddress(Address address, Direction direction, + Tuple updateElement) { + ReteContainer otherContainer = address.getContainer(); + long otherClock = otherContainer.sendUpdateToLocalAddress(address, direction, updateElement); + // Long criterion = terminationCriteria.get(otherContainer); + // if (criterion==null || otherClock > criterion) + terminationCriteria.put(otherContainer, otherClock); + } + + /** + * Finalises all update sequences and returns. To be called from user threads (e.g. network construction). + */ + public void flushUpdates() { + network.waitForReteTermination(); + // synchronized (messageQueue) + // { + // while (!messageQueue.isEmpty()) + // { + // try { + // UpdateMessage message = messageQueue.take(); + // message.receiver.update(message.direction, message.updateElement); + // } catch (InterruptedException e) {} + // } + // } + } + + /** + * Retrieves a safe copy of the contents of a supplier. + * + *

    Note that there may be multiple copies of a Tuple in case of a {@link TrimmerNode}, so the result is not always a set. + * + * @param flush if true, a flush is performed before pulling the contents + * @since 2.3 + */ + public Collection pullContents(final Supplier supplier, final boolean flush) { + if (flush) { + flushUpdates(); + } + final Collection collector = new ArrayList(); + supplier.pullInto(collector, flush); + return collector; + } + + /** + * @since 2.4 + */ + public Map> pullContentsWithTimeline(final Supplier supplier, final boolean flush) { + if (flush) { + flushUpdates(); + } + final Map> collector = CollectionsFactory.createMap(); + supplier.pullIntoWithTimeline(collector, flush); + return collector; + } + + /** + * Retrieves the contents of a SingleInputNode's parentage. + * + * @since 2.3 + */ + public Collection pullPropagatedContents(final SingleInputNode supplier, final boolean flush) { + if (flush) { + flushUpdates(); + } + final Collection collector = new LinkedList(); + supplier.propagatePullInto(collector, flush); + return collector; + } + + /** + * Retrieves the timestamp-aware contents of a SingleInputNode's parentage. + * + * @since 2.3 + */ + public Map> pullPropagatedContentsWithTimestamp(final SingleInputNode supplier, + final boolean flush) { + if (flush) { + flushUpdates(); + } + final Map> collector = CollectionsFactory.createMap(); + supplier.propagatePullIntoWithTimestamp(collector, flush); + return collector; + } + + /** + * Retrieves the contents of a supplier for a remote caller. Assumption is that this container is the home of the + * supplier, but it is not strictly necessary. + * + * @param supplier + * the address of the supplier to be pulled. + * @since 2.3 + */ + public Collection remotePull(Address supplier, boolean flush) { + if (!isLocal(supplier)) + return supplier.getContainer().remotePull(supplier, flush); + return pullContents(resolveLocal(supplier), flush); + } + + /** + * Proxies for the getPosMapping() of Production nodes. Retrieves the posmapping of a remote or local Production to + * a remote or local caller. + */ + public Map remotePosMapping(Address production) { + if (!isLocal(production)) + return production.getContainer().remotePosMapping(production); + return resolveLocal(production).getPosMapping(); + } + + /** + * Continually consumes update messages. Should be run on a dedicated thread. + */ + void messageConsumptionCycle() { + while (!killed) // deliver messages on and on and on.... + { + long incrementedClock = 0; + UpdateMessage message = null; + + if (!internalMessageQueue.isEmpty()) // take internal messages first + message = internalMessageQueue.removeFirst(); + else + // no internal message, take an incoming message + synchronized (externalMessageLock) { // no sleeping allowed, + // because external + // queue is locked for + // precise clocking of + // termination point! + if (!externalMessageQueue.isEmpty()) { // if external queue + // is non-empty, + // retrieve the next + // message instantly + message = takeExternalMessage(); + } else { // if external queue is found empty (and this is + // the first time in a row) + incrementedClock = ++clock; // local termination point + // synchronized(clock){incrementedClock = ++clock;} + } + } + + if (message == null) // both queues were empty + { + localUpdateTermination(incrementedClock); // report local + // termination point + while (message == null) // wait for a message while external + // queue is still empty + { + synchronized (externalMessageLock) { + while (externalMessageQueue.isEmpty()) { + try { + externalMessageLock.wait(); + } catch (InterruptedException e) { + if (killed) + return; + } + } + message = takeExternalMessage(); + } + + } + } + + // now we have a message to deliver + // NOTE: this method is not compatible with differential dataflow + message.receiver.update(message.direction, message.updateElement, Timestamp.ZERO); + } + } + + /** + * @since 1.6 + */ + public static final Function NAME_MAPPER = input -> input.toString().substring(0, + Math.min(30, input.toString().length())); + + /** + * Sends out all pending messages to their receivers. The delivery is governed by the communication tracker. + * + * @since 1.6 + */ + public void deliverMessagesSingleThreaded() { + if (!backendContext.areUpdatesDelayed()) { + if (Options.MONITOR_VIOLATION_OF_RETE_NODEGROUP_TOPOLOGICAL_SORTING) { + // known unreachable; enable for debugging only + + CommunicationGroup lastGroup = null; + Set seenInThisCycle = new HashSet<>(); + + while (!tracker.isEmpty()) { + final CommunicationGroup group = tracker.getAndRemoveFirstGroup(); + + /** + * The current group does not violate the communication schema iff (1) it was not seen before OR (2) + * the last one that was seen is exactly the same as the current one this can happen if the group + * was added back because of in-group message passing + */ + boolean okGroup = (group == lastGroup) || seenInThisCycle.add(group); + + if (!okGroup) { + logger.error( + "[INTERNAL ERROR] Violation of communication schema! The communication component with representative " + + group.getRepresentative() + " has already been processed!"); + } + + group.deliverMessages(); + + lastGroup = group; + } + + } else { + while (!tracker.isEmpty()) { + final CommunicationGroup group = tracker.getAndRemoveFirstGroup(); + group.deliverMessages(); + } + } + } + } + + private void localUpdateTermination(long incrementedClock) { + network.reportLocalUpdateTermination(this, incrementedClock, terminationCriteria); + terminationCriteria.clear(); + + // synchronized(clock){++clock;} // +1 incrementing for parity and easy + // comparison + } + + // @pre: externalMessageQueue synchronized && nonempty + private UpdateMessage takeExternalMessage() { + UpdateMessage message = externalMessageQueue.removeFirst(); + if (!externalMessageQueue.isEmpty()) { // copy the whole queue over + // for speedup + Deque temp = externalMessageQueue; + externalMessageQueue = internalMessageQueue; + internalMessageQueue = temp; + } + return message; + } + + /** + * Provides an external address for the selected node. + * + * @pre node belongs to this container. + */ + public Address makeAddress(N node) { + return new Address(node); + } + + /** + * Checks whether a certain address points to a node at this container. + */ + public boolean isLocal(Address address) { + return address.getContainer() == this; + } + + /** + * Returns an addressed node at this container. + * + * @pre: address.container == this, e.g. address MUST be local + * @throws IllegalArgumentException + * if address is non-local + */ + @SuppressWarnings("unchecked") + public N resolveLocal(Address address) { + if (this != address.getContainer()) + throw new IllegalArgumentException(String.format("Address %s non-local at container %s", address, this)); + + N cached = address.getNodeCache(); + if (cached != null) + return cached; + else { + N node = (N) nodesById.get(address.getNodeId()); + address.setNodeCache(node); + return node; + } + } + + /** + * Registers a node into the rete network (should be called by constructor). Every node MUST be registered by its + * constructor. + * + * @return the unique nodeId issued to the node. + */ + public long registerNode(Node n) { + long id = nextId++; + nodesById.put(id, n); + return id; + } + + /** + * Unregisters a node from the rete network. Do NOT call if node is still connected to other Nodes, or Adressed or + * otherwise referenced. + */ + public void unregisterNode(Node n) { + nodesById.remove(n.getNodeId()); + } + + /** + * Registers a pattern memory into the rete network. Every memory MUST be registered by its owner node. + */ + public void registerClearable(Clearable c) { + clearables.addFirst(c); + } + + /** + * Unregisters a pattern memory from the rete network. + */ + public void unregisterClearable(Clearable c) { + clearables.remove(c); + } + + /** + * Clears all memory contents in the network. Reverts to initial state. + */ + public void clearAll() { + for (Clearable c : clearables) { + c.clear(); + } + } + + public NodeFactory getNodeFactory() { + return network.getNodeFactory(); + } + + public ConnectionFactory getConnectionFactory() { + return connectionFactory; + } + + public NodeProvisioner getProvisioner() { + return nodeProvisioner; + } + + public Network getNetwork() { + return network; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + String separator = System.getProperty("line.separator"); + sb.append(super.toString() + "[[[" + separator); + java.util.List keys = new java.util.ArrayList(nodesById.keySet()); + java.util.Collections.sort(keys); + for (Long key : keys) { + sb.append(key + " -> " + nodesById.get(key) + separator); + } + sb.append("]]] of " + network); + return sb.toString(); + } + + /** + * Access all the Rete nodes inside this container. + * + * @return the collection of {@link Node} instances + */ + public Collection getAllNodes() { + return nodesById.values(); + } + + public InputConnector getInputConnectionFactory() { + return network.getInputConnector(); + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/StandardNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/StandardNode.java new file mode 100644 index 00000000..e7ec36dc --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/StandardNode.java @@ -0,0 +1,121 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.network; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.index.GenericProjectionIndexer; +import tools.refinery.viatra.runtime.rete.index.ProjectionIndexer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; +import tools.refinery.viatra.runtime.rete.traceability.TraceInfo; + +/** + * Base implementation for a supplier node. + * + * @author Gabor Bergmann + * + */ +public abstract class StandardNode extends BaseNode implements Supplier, NetworkStructureChangeSensitiveNode { + protected final List children = CollectionsFactory.createObserverList(); + /** + * @since 2.2 + */ + protected final List childMailboxes = CollectionsFactory.createObserverList(); + + public StandardNode(final ReteContainer reteContainer) { + super(reteContainer); + } + + /** + * @since 2.4 + */ + protected void propagateUpdate(final Direction direction, final Tuple updateElement, final Timestamp timestamp) { + for (final Mailbox childMailbox : childMailboxes) { + childMailbox.postMessage(direction, updateElement, timestamp); + } + } + + @Override + public void appendChild(final Receiver receiver) { + children.add(receiver); + childMailboxes.add(this.getCommunicationTracker().proxifyMailbox(this, receiver.getMailbox())); + } + + @Override + public void removeChild(final Receiver receiver) { + children.remove(receiver); + Mailbox mailboxToRemove = null; + for (final Mailbox mailbox : childMailboxes) { + if (mailbox.getReceiver() == receiver) { + mailboxToRemove = mailbox; + break; + } + } + assert mailboxToRemove != null; + childMailboxes.remove(mailboxToRemove); + } + + @Override + public void networkStructureChanged() { + childMailboxes.clear(); + for (final Receiver receiver : children) { + childMailboxes.add(this.getCommunicationTracker().proxifyMailbox(this, receiver.getMailbox())); + } + } + + @Override + public Collection getReceivers() { + return children; + } + + /** + * @since 2.2 + */ + public Collection getChildMailboxes() { + return this.childMailboxes; + } + + @Override + public Set getPulledContents(final boolean flush) { + final HashSet results = new HashSet(); + pullInto(results, flush); + return results; + } + + @Override + public ProjectionIndexer constructIndex(final TupleMask mask, final TraceInfo... traces) { + final GenericProjectionIndexer indexer = new GenericProjectionIndexer(reteContainer, mask); + for (final TraceInfo traceInfo : traces) { + indexer.assignTraceInfo(traceInfo); + } + reteContainer.connectAndSynchronize(this, indexer); + return indexer; + } + + /** + * @since 1.6 + */ + protected void issueError(final String message, final Exception ex) { + if (ex == null) { + this.reteContainer.getNetwork().getEngine().getLogger().error(message); + } else { + this.reteContainer.getNetwork().getEngine().getLogger().error(message, ex); + } + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Supplier.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Supplier.java new file mode 100644 index 00000000..1917a7cf --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Supplier.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.network; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.index.ProjectionIndexer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.single.TrimmerNode; +import tools.refinery.viatra.runtime.rete.traceability.TraceInfo; + +/** + * @author Gabor Bergmann + * + * A supplier is an object that can propagate insert or revoke events towards receivers. + */ +public interface Supplier extends Node { + + /** + * Pulls the contents of this object in this particular moment into a target collection. + * + * @param flush if true, flushing of messages is allowed during the pull, otherwise flushing is not allowed + * @since 2.3 + */ + public void pullInto(Collection collector, boolean flush); + + /** + * @since 2.4 + */ + public void pullIntoWithTimeline(final Map> collector, final boolean flush); + + /** + * Returns the contents of this object in this particular moment. + * For memoryless nodes, this may involve a costly recomputation of contents. + * + * The result is returned as a Set, even when it has multiplicities (at the output of {@link TrimmerNode}). + * + *

    Intended mainly for debug purposes; therefore flushing is performed only if explicitly requested + * During runtime, flushing may be preferred; see {@link ReteContainer#pullContents(Supplier)} + * @since 2.3 + */ + public Set getPulledContents(boolean flush); + + default public Set getPulledContents() { + return getPulledContents(true); + } + + /** + * appends a receiver that will continously receive insert and revoke updates from this supplier + */ + void appendChild(Receiver receiver); + + /** + * removes a receiver + */ + void removeChild(Receiver receiver); + + /** + * Instantiates (or reuses, depending on implementation) an index according to the given mask. + * + * Intended for internal use; clients should invoke through Library instead to enable reusing. + */ + ProjectionIndexer constructIndex(TupleMask mask, TraceInfo... traces); + + /** + * lists receivers + */ + Collection getReceivers(); + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Tunnel.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Tunnel.java new file mode 100644 index 00000000..f238f47b --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/Tunnel.java @@ -0,0 +1,19 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.network; + +/** + * @author Gabor Bergmann + * + * A Tunnel is an interface into which elments can be instered and from which productions can be extracted. + */ +public interface Tunnel extends Supplier, Receiver { + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/UpdateMessage.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/UpdateMessage.java new file mode 100644 index 00000000..1334a3a9 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/UpdateMessage.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.network; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; + +class UpdateMessage { + public Receiver receiver; + public Direction direction; + public Tuple updateElement; + + public UpdateMessage(Receiver receiver, Direction direction, Tuple updateElement) { + this.receiver = receiver; + this.direction = direction; + this.updateElement = updateElement; + } + + @Override + public String toString() { + return "M." + direction + ": " + updateElement + " -> " + receiver; + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/CommunicationGroup.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/CommunicationGroup.java new file mode 100644 index 00000000..8cedeb11 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/CommunicationGroup.java @@ -0,0 +1,103 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.communication; + +import java.util.Collection; +import java.util.Map; + +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; + +/** + * A communication group represents a set of nodes in the communication graph that form a strongly connected component. + * + * @author Tamas Szabo + * @since 1.6 + */ +public abstract class CommunicationGroup implements Comparable { + + public static final String UNSUPPORTED_MESSAGE_KIND = "Unsupported message kind "; + + /** + * Marker for the {@link CommunicationTracker} + */ + public boolean isEnqueued = false; + + protected final Node representative; + + /** + * May be changed during bumping in {@link CommunicationTracker.registerDependency} + */ + protected int identifier; + + /** + * @since 1.7 + */ + protected final CommunicationTracker tracker; + + /** + * @since 1.7 + */ + public CommunicationGroup(final CommunicationTracker tracker, final Node representative, final int identifier) { + this.tracker = tracker; + this.representative = representative; + this.identifier = identifier; + } + + public abstract void deliverMessages(); + + public Node getRepresentative() { + return representative; + } + + public abstract boolean isEmpty(); + + /** + * @since 2.0 + */ + public abstract void notifyLostAllMessages(final Mailbox mailbox, final MessageSelector kind); + + /** + * @since 2.0 + */ + public abstract void notifyHasMessage(final Mailbox mailbox, final MessageSelector kind); + + public abstract Map> getMailboxes(); + + public abstract boolean isRecursive(); + + @Override + public int hashCode() { + return this.identifier; + } + + @Override + public String toString() { + return this.getClass().getSimpleName() + " " + this.identifier + " - representative: " + this.representative + + " - isEmpty: " + isEmpty(); + } + + @Override + public boolean equals(final Object obj) { + if (obj == null || this.getClass() != obj.getClass()) { + return false; + } else if (this == obj) { + return true; + } else { + final CommunicationGroup that = (CommunicationGroup) obj; + return this.identifier == that.identifier; + } + } + + @Override + public int compareTo(final CommunicationGroup that) { + return this.identifier - that.identifier; + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/CommunicationTracker.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/CommunicationTracker.java new file mode 100644 index 00000000..8435a547 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/CommunicationTracker.java @@ -0,0 +1,467 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.communication; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.PriorityQueue; +import java.util.Queue; +import java.util.Set; + +import tools.refinery.viatra.runtime.base.itc.alg.incscc.IncSCCAlg; +import tools.refinery.viatra.runtime.base.itc.alg.misc.topsort.TopologicalSorting; +import tools.refinery.viatra.runtime.base.itc.graphimpl.Graph; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.rete.aggregation.IAggregatorNode; +import tools.refinery.viatra.runtime.rete.boundary.ExternalInputEnumeratorNode; +import tools.refinery.viatra.runtime.rete.eval.RelationEvaluatorNode; +import tools.refinery.viatra.runtime.rete.index.DualInputNode; +import tools.refinery.viatra.runtime.rete.index.ExistenceNode; +import tools.refinery.viatra.runtime.rete.index.Indexer; +import tools.refinery.viatra.runtime.rete.index.IndexerListener; +import tools.refinery.viatra.runtime.rete.index.IterableIndexer; +import tools.refinery.viatra.runtime.rete.index.SpecializedProjectionIndexer; +import tools.refinery.viatra.runtime.rete.network.IGroupable; +import tools.refinery.viatra.runtime.rete.network.NetworkStructureChangeSensitiveNode; +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.ProductionNode; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.timely.TimelyIndexerListenerProxy; +import tools.refinery.viatra.runtime.rete.network.communication.timely.TimelyMailboxProxy; +import tools.refinery.viatra.runtime.rete.network.mailbox.FallThroughCapableMailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.timeless.BehaviorChangingMailbox; +import tools.refinery.viatra.runtime.rete.single.TransitiveClosureNode; +import tools.refinery.viatra.runtime.rete.single.TrimmerNode; + +/** + * An instance of this class is associated with every {@link ReteContainer}. The tracker serves two purposes:
    + * (1) It allows RETE nodes to register their communication dependencies on-the-fly. These dependencies can be + * registered or unregistered when nodes are disposed of.
    + * (2) It allows RETE nodes to register their mailboxes as dirty, that is, they can tell the tracker that they have + * something to send to other nodes in the network. The tracker is then responsible for ordering these messages (more + * precisely, the mailboxes that contain the messages) for the associated {@link ReteContainer}. The ordering is + * governed by the strongly connected components in the dependency network and follows a topological sorting scheme; + * those mailboxes will be emptied first whose owner nodes do not depend on other undelivered messages. + * + * @author Tamas Szabo + * @since 1.6 + * + */ +public abstract class CommunicationTracker { + + /** + * The minimum group id assigned so far + */ + protected int minGroupId; + + /** + * The maximum group id assigned so far + */ + protected int maxGroupId; + + /** + * The dependency graph of the communications in the RETE network + */ + protected final Graph dependencyGraph; + + /** + * Incremental SCC information about the dependency graph + */ + protected final IncSCCAlg sccInformationProvider; + + /** + * Precomputed node -> communication group map + */ + protected final Map groupMap; + + /** + * Priority queue of active communication groups + */ + protected final Queue groupQueue; + + // groups should have a simple integer flag which represents its position in a priority queue + // priority queue only contains the ACTIVE groups + + public CommunicationTracker() { + this.dependencyGraph = new Graph(); + this.sccInformationProvider = new IncSCCAlg(this.dependencyGraph); + this.groupQueue = new PriorityQueue(); + this.groupMap = new HashMap(); + } + + public Graph getDependencyGraph() { + return dependencyGraph; + } + + public CommunicationGroup getGroup(final Node node) { + return this.groupMap.get(node); + } + + private void precomputeGroups() { + groupMap.clear(); + + // reconstruct group map from dependency graph + final Graph reducedGraph = sccInformationProvider.getReducedGraph(); + final List representatives = TopologicalSorting.compute(reducedGraph); + + for (int i = 0; i < representatives.size(); i++) { // groups for SCC representatives + final Node representative = representatives.get(i); + createAndStoreGroup(representative, i); + } + + minGroupId = 0; + maxGroupId = representatives.size() - 1; + + for (final Node node : dependencyGraph.getAllNodes()) { // extend group map to the rest of nodes + final Node representative = sccInformationProvider.getRepresentative(node); + final CommunicationGroup group = groupMap.get(representative); + if (representative != node) { + addToGroup(node, group); + } + } + + for (final Node node : dependencyGraph.getAllNodes()) { + // set fall-through flags of default mailboxes + precomputeFallThroughFlag(node); + // perform further tracker-specific post-processing + postProcessNode(node); + } + + // reconstruct new queue contents based on new group map + if (!groupQueue.isEmpty()) { + final Set oldActiveGroups = new HashSet(groupQueue); + groupQueue.clear(); + reconstructQueueContents(oldActiveGroups); + } + + // post process the groups + for (final CommunicationGroup group : groupMap.values()) { + postProcessGroup(group); + } + } + + /** + * This method is responsible for reconstructing the active queue contents after the network structure has changed. + * It it defined as abstract because the reconstruction logic is specific to each {@link CommunicationTracker}. + * @since 2.4 + */ + protected abstract void reconstructQueueContents(final Set oldActiveGroups); + + private void addToGroup(final Node node, final CommunicationGroup group) { + groupMap.put(node, group); + if (node instanceof Receiver) { + ((Receiver) node).getMailbox().setCurrentGroup(group); + if (node instanceof IGroupable) { + ((IGroupable) node).setCurrentGroup(group); + } + } + } + + /** + * Depends on the groups, as well as the parent nodes of the argument, so recomputation is needed if these change + */ + private void precomputeFallThroughFlag(final Node node) { + CommunicationGroup group = groupMap.get(node); + if (node instanceof Receiver) { + IGroupable mailbox = ((Receiver) node).getMailbox(); + if (mailbox instanceof FallThroughCapableMailbox) { + Set directParents = dependencyGraph.getSourceNodes(node).distinctValues(); + // decide between using quick&cheap fall-through, or allowing for update cancellation + boolean fallThrough = + // disallow fallthrough: updates at production nodes should cancel, if they can be trimmed or + // disjunctive + (!(node instanceof ProductionNode && ( // it is a production node... + // with more than one parent + directParents.size() > 0 || + // or true trimming in its sole parent + directParents.size() == 1 && trueTrimming(directParents.iterator().next())))) && + // disallow fallthrough: external updates should be stored (if updates are delayed) + (!(node instanceof ExternalInputEnumeratorNode)) && + // disallow fallthrough: RelationEvaluatorNode needs to be notified in batch-style, and the batching is done by the mailbox + // however, it is not the RelationEvaluatorNode itself that is interesting here, as that indirectly uses the BatchingReceiver + // so we need to disable fall-through for the BatchingReceiver + (!(node instanceof RelationEvaluatorNode.BatchingReceiver)); + // do additional checks + if (fallThrough) { + // recursive parent groups generate excess updates that should be cancelled after delete&rederive + // phases + // aggregator and transitive closure parent nodes also generate excess updates that should be + // cancelled + directParentLoop: for (Node directParent : directParents) { + Set parentsToCheck = new HashSet<>(); + // check the case where a direct parent is the reason for mailbox usage + parentsToCheck.add(directParent); + // check the case where an indirect parent (join slot) is the reason for mailbox usage + if (directParent instanceof DualInputNode) { + // in case of existence join (typically antijoin), a mailbox should allow + // an insertion and deletion (at the secondary slot) to cancel each other out + if (directParent instanceof ExistenceNode) { + fallThrough = false; + break directParentLoop; + } + // in beta nodes, indexer slots (or their active nodes) are considered indirect parents + DualInputNode dualInput = (DualInputNode) directParent; + IterableIndexer primarySlot = dualInput.getPrimarySlot(); + if (primarySlot != null) + parentsToCheck.add(primarySlot.getActiveNode()); + Indexer secondarySlot = dualInput.getSecondarySlot(); + if (secondarySlot != null) + parentsToCheck.add(secondarySlot.getActiveNode()); + } + for (Node parent : parentsToCheck) { + CommunicationGroup parentGroup = groupMap.get(parent); + if ( // parent is in a different, recursive group + (group != parentGroup && parentGroup.isRecursive()) || + // node and parent within the same recursive group, and... + (group == parentGroup && group.isRecursive() && ( + // parent is a transitive closure or aggregator node, or a trimmer + // allow trimmed or disjunctive tuple updates to cancel each other + (parent instanceof TransitiveClosureNode) || (parent instanceof IAggregatorNode) + || trueTrimming(parent)))) { + fallThrough = false; + break directParentLoop; + } + } + } + } + // overwrite fallthrough flag with newly computed value + ((FallThroughCapableMailbox) mailbox).setFallThrough(fallThrough); + } + } + } + + /** + * A trimmer node that actually eliminates some columns (not just reorders) + */ + private boolean trueTrimming(Node node) { + if (node instanceof TrimmerNode) { + TupleMask mask = ((TrimmerNode) node).getMask(); + return (mask.indices.length != mask.sourceWidth); + } + return false; + } + + public void activateUnenqueued(final CommunicationGroup group) { + groupQueue.add(group); + group.isEnqueued = true; + } + + public void deactivate(final CommunicationGroup group) { + groupQueue.remove(group); + group.isEnqueued = false; + } + + public CommunicationGroup getAndRemoveFirstGroup() { + final CommunicationGroup group = groupQueue.poll(); + group.isEnqueued = false; + return group; + } + + public boolean isEmpty() { + return groupQueue.isEmpty(); + } + + protected abstract CommunicationGroup createGroup(final Node representative, final int index); + + protected CommunicationGroup createAndStoreGroup(final Node representative, final int index) { + final CommunicationGroup group = createGroup(representative, index); + addToGroup(representative, group); + return group; + } + + /** + * Registers the dependency that the target {@link Node} depends on the source {@link Node}. In other words, source + * may send messages to target in the RETE network. If the dependency edge is already present, this method call is a + * noop. + * + * @param source + * the source node + * @param target + * the target node + */ + public void registerDependency(final Node source, final Node target) { + // nodes can be immediately inserted, if they already exist in the graph, this is a noop + dependencyGraph.insertNode(source); + dependencyGraph.insertNode(target); + + if (!this.dependencyGraph.getTargetNodes(source).containsNonZero(target)) { + + // query all these information before the actual edge insertion + // because SCCs may be unified during the process + final Node sourceRepresentative = sccInformationProvider.getRepresentative(source); + final Node targetRepresentative = sccInformationProvider.getRepresentative(target); + final boolean targetHadOutgoingEdges = sccInformationProvider.hasOutgoingEdges(targetRepresentative); + + // insert the edge + dependencyGraph.insertEdge(source, target); + + // create groups if they do not yet exist + CommunicationGroup sourceGroup = groupMap.get(sourceRepresentative); + if (sourceGroup == null) { + // create on-demand with the next smaller group id + sourceGroup = createAndStoreGroup(sourceRepresentative, --minGroupId); + } + final int sourceIndex = sourceGroup.identifier; + + CommunicationGroup targetGroup = groupMap.get(targetRepresentative); + if (targetGroup == null) { + // create on-demand with the next larger group id + targetGroup = createAndStoreGroup(targetRepresentative, ++maxGroupId); + } + final int targetIndex = targetGroup.identifier; + + if (sourceIndex <= targetIndex) { + // indices obey current topological ordering + refreshFallThroughFlag(target); + postProcessNode(source); + postProcessNode(target); + postProcessGroup(sourceGroup); + if (sourceGroup != targetGroup) { + postProcessGroup(targetGroup); + } + } else if (sourceIndex > targetIndex && !targetHadOutgoingEdges) { + // indices violate current topological ordering, but we can simply bump the target index + final boolean wasEnqueued = targetGroup.isEnqueued; + if (wasEnqueued) { + groupQueue.remove(targetGroup); + } + targetGroup.identifier = ++maxGroupId; + if (wasEnqueued) { + groupQueue.add(targetGroup); + } + + refreshFallThroughFlag(target); + postProcessNode(source); + postProcessNode(target); + postProcessGroup(sourceGroup); + postProcessGroup(targetGroup); + } else { + // needs a full re-computation because of more complex change + precomputeGroups(); + } + } + } + + /** + * Returns true if the given {@link Node} is in a recursive {@link CommunicationGroup}, false otherwise. + */ + public boolean isInRecursiveGroup(final Node node) { + final CommunicationGroup group = this.getGroup(node); + if (group == null) { + return false; + } else { + return group.isRecursive(); + } + } + + /** + * Returns true if the given two {@link Node}s are in the same {@link CommunicationGroup}. + */ + public boolean areInSameGroup(final Node left, final Node right) { + final CommunicationGroup leftGroup = this.getGroup(left); + final CommunicationGroup rightGroup = this.getGroup(right); + return leftGroup != null && leftGroup == rightGroup; + } + + /** + * Unregisters a dependency between source and target. + * + * @param source + * the source node + * @param target + * the target node + */ + public void unregisterDependency(final Node source, final Node target) { + // delete the edge first, and then query the SCC info provider + this.dependencyGraph.deleteEdgeIfExists(source, target); + + final Node sourceRepresentative = sccInformationProvider.getRepresentative(source); + final Node targetRepresentative = sccInformationProvider.getRepresentative(target); + + // if they are still in the same SCC, + // then this deletion did not affect the SCCs, + // and it is sufficient to recompute affected fall-through flags; + // otherwise, we need a new pre-computation for the groupMap and groupQueue + if (sourceRepresentative.equals(targetRepresentative)) { + // this deletion could not have affected the split flags + refreshFallThroughFlag(target); + postProcessNode(source); + postProcessNode(target); + } else { + // preComputeGroups takes care of the split flag maintenance + precomputeGroups(); + } + } + + /** + * Refresh fall-through flags if dependencies change for given target, but no SCC change + */ + private void refreshFallThroughFlag(final Node target) { + precomputeFallThroughFlag(target); + if (target instanceof DualInputNode) { + for (final Node indirectTarget : dependencyGraph.getTargetNodes(target).distinctValues()) { + precomputeFallThroughFlag(indirectTarget); + } + } + } + + /** + * Returns true if the given source-target edge in the communication network acts as a recursion cut point. + * The current implementation considers edges leading into {@link ProductionNode}s as cut point iff + * both source and target belong to the same group. + * + * @param source the source node + * @param target the target node + * @return true if the edge is a cut point, false otherwise + * @since 2.4 + */ + protected boolean isRecursionCutPoint(final Node source, final Node target) { + final Node effectiveSource = source instanceof SpecializedProjectionIndexer + ? ((SpecializedProjectionIndexer) source).getActiveNode() + : source; + final CommunicationGroup sourceGroup = this.getGroup(effectiveSource); + final CommunicationGroup targetGroup = this.getGroup(target); + return sourceGroup != null && sourceGroup == targetGroup && target instanceof ProductionNode; + } + + /** + * This hook allows concrete tracker implementations to perform tracker-specific post processing on nodes (cf. + * {@link NetworkStructureChangeSensitiveNode} and {@link BehaviorChangingMailbox}). At the time of the invocation, + * the network topology has already been updated. + */ + protected abstract void postProcessNode(final Node node); + + /** + * This hook allows concrete tracker implementations to perform tracker-specific post processing on groups. At the + * time of the invocation, the network topology has already been updated. + * @since 2.4 + */ + protected abstract void postProcessGroup(final CommunicationGroup group); + + /** + * Creates a proxy for the given {@link Mailbox} for the given requester {@link Node}. The proxy creation is + * {@link CommunicationTracker}-specific and depends on the identity of the requester. This method is primarily used + * to create {@link TimelyMailboxProxy}s depending on the network topology. There is no guarantee that the same + * proxy instance is returned when this method is called multiple times with the same arguments. + */ + public abstract Mailbox proxifyMailbox(final Node requester, final Mailbox original); + + /** + * Creates a proxy for the given {@link IndexerListener} for the given requester {@link Node}. The proxy creation is + * {@link CommunicationTracker}-specific and depends on the identity of the requester. This method is primarily used + * to create {@link TimelyIndexerListenerProxy}s depending on the network topology. There is no guarantee that the + * same proxy instance is returned when this method is called multiple times with the same arguments. + */ + public abstract IndexerListener proxifyIndexerListener(final Node requester, final IndexerListener original); + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/MessageSelector.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/MessageSelector.java new file mode 100644 index 00000000..e1a61693 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/MessageSelector.java @@ -0,0 +1,19 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.communication; + +/** + * Subclasses of this interface represent meta data of update messages in Rete. + * + * @author Tamas Szabo + * @since 2.3 + */ +public interface MessageSelector { + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/NodeComparator.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/NodeComparator.java new file mode 100644 index 00000000..27779352 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/NodeComparator.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.communication; + +import java.util.Comparator; +import java.util.Map; + +import tools.refinery.viatra.runtime.rete.network.Node; + +/** + * @since 2.4 + */ +public class NodeComparator implements Comparator { + + protected final Map nodeMap; + + public NodeComparator(final Map nodeMap) { + this.nodeMap = nodeMap; + } + + @Override + public int compare(final Node left, final Node right) { + return this.nodeMap.get(left) - this.nodeMap.get(right); + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/PhasedSelector.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/PhasedSelector.java new file mode 100644 index 00000000..41cd8cd3 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/PhasedSelector.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.communication; + +/** + * A default message selector that can be used to associate phases to messages. + * + * @author Tamas Szabo + * @since 2.3 + */ +public enum PhasedSelector implements MessageSelector { + + /** + * No special distinguishing feature + */ + DEFAULT, + + /** + * Inserts and delete-insert monotone change pairs + */ + MONOTONE, + + /** + * Deletes + */ + ANTI_MONOTONE + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/Timestamp.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/Timestamp.java new file mode 100644 index 00000000..a50a63a8 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/Timestamp.java @@ -0,0 +1,124 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.communication; + +import java.util.AbstractMap; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timelines; + +/** + * A timestamp associated with update messages in timely evaluation. + * + * @author Tamas Szabo + * @since 2.3 + */ +public class Timestamp implements Comparable, MessageSelector { + + protected final int value; + public static final Timestamp ZERO = new Timestamp(0); + /** + * @since 2.4 + */ + public static final Timeline INSERT_AT_ZERO_TIMELINE = Timelines.createFrom(Timestamp.ZERO); + + public Timestamp(final int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public Timestamp max(final Timestamp that) { + if (this.value >= that.value) { + return this; + } else { + return that; + } + } + + /** + * @since 2.4 + */ + public Timestamp min(final Timestamp that) { + if (this.value <= that.value) { + return this; + } else { + return that; + } + } + + @Override + public int compareTo(final Timestamp that) { + return this.value - that.value; + } + + @Override + public boolean equals(final Object obj) { + if (obj == null || !(obj instanceof Timestamp)) { + return false; + } else { + return this.value == ((Timestamp) obj).value; + } + } + + @Override + public int hashCode() { + return this.value; + } + + @Override + public String toString() { + return Integer.toString(this.value); + } + + /** + * A {@link Map} implementation that associates the zero timestamp with every key. There is no suppor for + * {@link Map#entrySet()} due to performance reasons. + * + * @author Tamas Szabo + */ + public static final class AllZeroMap extends AbstractMap> { + + private final Collection wrapped; + + public AllZeroMap(Set wrapped) { + this.wrapped = wrapped; + } + + @Override + public Set>> entrySet() { + throw new UnsupportedOperationException("Use the combination of keySet() and get()!"); + } + + /** + * @since 2.4 + */ + @Override + public Timeline get(final Object key) { + return INSERT_AT_ZERO_TIMELINE; + } + + @Override + public Set keySet() { + return (Set) this.wrapped; + } + + @Override + public String toString() { + return this.getClass().getSimpleName() + ": " + this.keySet().toString(); + } + + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timeless/RecursiveCommunicationGroup.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timeless/RecursiveCommunicationGroup.java new file mode 100644 index 00000000..d8260384 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timeless/RecursiveCommunicationGroup.java @@ -0,0 +1,164 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.communication.timeless; + +import java.util.Collection; +import java.util.Collections; +import java.util.EnumMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.RederivableNode; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker; +import tools.refinery.viatra.runtime.rete.network.communication.MessageSelector; +import tools.refinery.viatra.runtime.rete.network.communication.PhasedSelector; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; + +/** + * A communication group representing either a single node where the + * node is a monotonicity aware one or a set of nodes that form an SCC. + * + * @author Tamas Szabo + * @since 2.4 + */ +public class RecursiveCommunicationGroup extends CommunicationGroup { + + private final Set antiMonotoneMailboxes; + private final Set monotoneMailboxes; + private final Set defaultMailboxes; + private final Set rederivables; + private boolean currentlyDelivering; + + /** + * @since 1.7 + */ + public RecursiveCommunicationGroup(final CommunicationTracker tracker, final Node representative, final int identifier) { + super(tracker, representative, identifier); + this.antiMonotoneMailboxes = CollectionsFactory.createSet(); + this.monotoneMailboxes = CollectionsFactory.createSet(); + this.defaultMailboxes = CollectionsFactory.createSet(); + this.rederivables = new LinkedHashSet(); + this.currentlyDelivering = false; + } + + @Override + public void deliverMessages() { + this.currentlyDelivering = true; + + // ANTI-MONOTONE PHASE + while (!this.antiMonotoneMailboxes.isEmpty() || !this.defaultMailboxes.isEmpty()) { + while (!this.antiMonotoneMailboxes.isEmpty()) { + final Mailbox mailbox = this.antiMonotoneMailboxes.iterator().next(); + this.antiMonotoneMailboxes.remove(mailbox); + mailbox.deliverAll(PhasedSelector.ANTI_MONOTONE); + } + while (!this.defaultMailboxes.isEmpty()) { + final Mailbox mailbox = this.defaultMailboxes.iterator().next(); + this.defaultMailboxes.remove(mailbox); + mailbox.deliverAll(PhasedSelector.DEFAULT); + } + } + + // REDERIVE PHASE + while (!this.rederivables.isEmpty()) { + // re-derivable nodes take care of their unregistration!! + final RederivableNode node = this.rederivables.iterator().next(); + node.rederiveOne(); + } + + // MONOTONE PHASE + while (!this.monotoneMailboxes.isEmpty() || !this.defaultMailboxes.isEmpty()) { + while (!this.monotoneMailboxes.isEmpty()) { + final Mailbox mailbox = this.monotoneMailboxes.iterator().next(); + this.monotoneMailboxes.remove(mailbox); + mailbox.deliverAll(PhasedSelector.MONOTONE); + } + while (!this.defaultMailboxes.isEmpty()) { + final Mailbox mailbox = this.defaultMailboxes.iterator().next(); + this.defaultMailboxes.remove(mailbox); + mailbox.deliverAll(PhasedSelector.DEFAULT); + } + } + + this.currentlyDelivering = false; + } + + @Override + public boolean isEmpty() { + return this.rederivables.isEmpty() && this.antiMonotoneMailboxes.isEmpty() + && this.monotoneMailboxes.isEmpty() && this.defaultMailboxes.isEmpty(); + } + + @Override + public void notifyHasMessage(final Mailbox mailbox, final MessageSelector kind) { + final Collection mailboxes = getMailboxContainer(kind); + mailboxes.add(mailbox); + if (!this.isEnqueued && !this.currentlyDelivering) { + this.tracker.activateUnenqueued(this); + } + } + + @Override + public void notifyLostAllMessages(final Mailbox mailbox, final MessageSelector kind) { + final Collection mailboxes = getMailboxContainer(kind); + mailboxes.remove(mailbox); + if (isEmpty()) { + this.tracker.deactivate(this); + } + } + + private Collection getMailboxContainer(final MessageSelector kind) { + if (kind == PhasedSelector.ANTI_MONOTONE) { + return this.antiMonotoneMailboxes; + } else if (kind == PhasedSelector.MONOTONE) { + return this.monotoneMailboxes; + } else if (kind == PhasedSelector.DEFAULT) { + return this.defaultMailboxes; + } else { + throw new IllegalArgumentException(UNSUPPORTED_MESSAGE_KIND + kind); + } + } + + public void addRederivable(final RederivableNode node) { + this.rederivables.add(node); + if (!this.isEnqueued) { + this.tracker.activateUnenqueued(this); + } + } + + public void removeRederivable(final RederivableNode node) { + this.rederivables.remove(node); + if (isEmpty()) { + this.tracker.deactivate(this); + } + } + + public Collection getRederivables() { + return this.rederivables; + } + + @Override + public Map> getMailboxes() { + Map> map = new EnumMap<>(PhasedSelector.class); + map.put(PhasedSelector.ANTI_MONOTONE, antiMonotoneMailboxes); + map.put(PhasedSelector.MONOTONE, monotoneMailboxes); + map.put(PhasedSelector.DEFAULT, defaultMailboxes); + return Collections.unmodifiableMap(map); + } + + @Override + public boolean isRecursive() { + return true; + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timeless/SingletonCommunicationGroup.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timeless/SingletonCommunicationGroup.java new file mode 100644 index 00000000..c51c7dbf --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timeless/SingletonCommunicationGroup.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.communication.timeless; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker; +import tools.refinery.viatra.runtime.rete.network.communication.MessageSelector; +import tools.refinery.viatra.runtime.rete.network.communication.PhasedSelector; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; + +/** + * A communication group containing only a single node with a single default + * mailbox. + * + * @author Tamas Szabo + * @since 1.6 + */ +public class SingletonCommunicationGroup extends CommunicationGroup { + + private Mailbox mailbox; + + /** + * @since 1.7 + */ + public SingletonCommunicationGroup(final CommunicationTracker tracker, final Node representative, final int identifier) { + super(tracker, representative, identifier); + } + + @Override + public void deliverMessages() { + this.mailbox.deliverAll(PhasedSelector.DEFAULT); + } + + @Override + public boolean isEmpty() { + return this.mailbox == null; + } + + @Override + public void notifyHasMessage(final Mailbox mailbox, final MessageSelector kind) { + if (kind == PhasedSelector.DEFAULT) { + this.mailbox = mailbox; + if (!this.isEnqueued) { + this.tracker.activateUnenqueued(this); + } + } else { + throw new IllegalArgumentException(UNSUPPORTED_MESSAGE_KIND + kind); + } + } + + @Override + public void notifyLostAllMessages(final Mailbox mailbox, final MessageSelector kind) { + if (kind == PhasedSelector.DEFAULT) { + this.mailbox = null; + this.tracker.deactivate(this); + } else { + throw new IllegalArgumentException(UNSUPPORTED_MESSAGE_KIND + kind); + } + } + + @Override + public Map> getMailboxes() { + if (mailbox != null) { + return Collections.singletonMap(PhasedSelector.DEFAULT, Collections.singleton(mailbox)); + } else { + return Collections.emptyMap(); + } + } + + @Override + public boolean isRecursive() { + return false; + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timeless/TimelessCommunicationTracker.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timeless/TimelessCommunicationTracker.java new file mode 100644 index 00000000..1c18c1cd --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timeless/TimelessCommunicationTracker.java @@ -0,0 +1,149 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.communication.timeless; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.Map.Entry; + +import tools.refinery.viatra.runtime.rete.index.DualInputNode; +import tools.refinery.viatra.runtime.rete.index.Indexer; +import tools.refinery.viatra.runtime.rete.index.IndexerListener; +import tools.refinery.viatra.runtime.rete.index.IterableIndexer; +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.RederivableNode; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker; +import tools.refinery.viatra.runtime.rete.network.communication.MessageSelector; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.timeless.BehaviorChangingMailbox; + +/** + * Timeless implementation of the communication tracker. + * + * @author Tamas Szabo + * @since 2.2 + */ +public class TimelessCommunicationTracker extends CommunicationTracker { + + @Override + protected CommunicationGroup createGroup(Node representative, int index) { + final boolean isSingleton = this.sccInformationProvider.sccs.getPartition(representative).size() == 1; + final boolean isReceiver = representative instanceof Receiver; + final boolean isPosetIndifferent = isReceiver + && ((Receiver) representative).getMailbox() instanceof BehaviorChangingMailbox; + final boolean isSingletonInDRedMode = isSingleton && (representative instanceof RederivableNode) + && ((RederivableNode) representative).isInDRedMode(); + + CommunicationGroup group = null; + // we can only use a singleton group iff + // (1) the SCC has one node AND + // (2) either we have a poset-indifferent mailbox OR the node is not even a receiver AND + // (3) the node does not run in DRed mode in a singleton group + if (isSingleton && (isPosetIndifferent || !isReceiver) && !isSingletonInDRedMode) { + group = new SingletonCommunicationGroup(this, representative, index); + } else { + group = new RecursiveCommunicationGroup(this, representative, index); + } + + return group; + } + + @Override + protected void reconstructQueueContents(final Set oldActiveGroups) { + for (final CommunicationGroup oldGroup : oldActiveGroups) { + for (final Entry> entry : oldGroup.getMailboxes().entrySet()) { + for (final Mailbox mailbox : entry.getValue()) { + final CommunicationGroup newGroup = this.groupMap.get(mailbox.getReceiver()); + newGroup.notifyHasMessage(mailbox, entry.getKey()); + } + } + + if (oldGroup instanceof RecursiveCommunicationGroup) { + for (final RederivableNode node : ((RecursiveCommunicationGroup) oldGroup).getRederivables()) { + final CommunicationGroup newGroup = this.groupMap.get(node); + if (!(newGroup instanceof RecursiveCommunicationGroup)) { + throw new IllegalStateException("The new group must also be recursive! " + newGroup); + } + ((RecursiveCommunicationGroup) newGroup).addRederivable(node); + } + } + } + } + + @Override + public Mailbox proxifyMailbox(final Node requester, final Mailbox original) { + return original; + } + + @Override + public IndexerListener proxifyIndexerListener(final Node requester, final IndexerListener original) { + return original; + } + + @Override + protected void postProcessNode(final Node node) { + if (node instanceof Receiver) { + final Mailbox mailbox = ((Receiver) node).getMailbox(); + if (mailbox instanceof BehaviorChangingMailbox) { + final CommunicationGroup group = this.groupMap.get(node); + final Set sccNodes = this.sccInformationProvider.sccs.getPartition(node); + // a default mailbox must split its messages iff + // (1) its receiver is in a recursive group and + final boolean c1 = group.isRecursive(); + // (2) its receiver is at the SCC boundary of that group + final boolean c2 = isAtSCCBoundary(node); + // (3) its group consists of more than one node + final boolean c3 = sccNodes.size() > 1; + ((BehaviorChangingMailbox) mailbox).setSplitFlag(c1 && c2 && c3); + } + } + } + + @Override + protected void postProcessGroup(final CommunicationGroup group) { + + } + + /** + * @since 2.0 + */ + private boolean isAtSCCBoundary(final Node node) { + final CommunicationGroup ownGroup = this.groupMap.get(node); + assert ownGroup != null; + for (final Node source : this.dependencyGraph.getSourceNodes(node).distinctValues()) { + final Set sourcesToCheck = new HashSet(); + sourcesToCheck.add(source); + // DualInputNodes must be checked additionally because they do not use a mailbox directly. + // It can happen that their indexers actually belong to other SCCs. + if (source instanceof DualInputNode) { + final DualInputNode dualInput = (DualInputNode) source; + final IterableIndexer primarySlot = dualInput.getPrimarySlot(); + if (primarySlot != null) { + sourcesToCheck.add(primarySlot.getActiveNode()); + } + final Indexer secondarySlot = dualInput.getSecondarySlot(); + if (secondarySlot != null) { + sourcesToCheck.add(secondarySlot.getActiveNode()); + } + } + for (final Node current : sourcesToCheck) { + final CommunicationGroup otherGroup = this.groupMap.get(current); + assert otherGroup != null; + if (!ownGroup.equals(otherGroup)) { + return true; + } + } + } + return false; + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/ResumableNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/ResumableNode.java new file mode 100644 index 00000000..8097bd91 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/ResumableNode.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.communication.timely; + +import tools.refinery.viatra.runtime.rete.network.IGroupable; +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * {@link Node}s that implement this interface can resume folding of their states when instructed during timely evaluation. + * + * @since 2.3 + * @author Tamas Szabo + */ +public interface ResumableNode extends Node, IGroupable { + + /** + * When called, the folding of the state shall be resumed at the given timestamp. The resumable is expected to + * do a folding step at the given timestamp only. Afterwards, folding shall be interrupted, even if there is more + * folding to do towards higher timestamps. + */ + public void resumeAt(final Timestamp timestamp); + + /** + * Returns the smallest timestamp where lazy folding shall be resumed, or null if there is no more folding to do in this + * resumable. + */ + public Timestamp getResumableTimestamp(); + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimelyCommunicationGroup.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimelyCommunicationGroup.java new file mode 100644 index 00000000..0394d92c --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimelyCommunicationGroup.java @@ -0,0 +1,171 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.communication.timely; + +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; + +import org.apache.log4j.Logger; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.MessageSelector; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.timely.TimelyMailbox; +import tools.refinery.viatra.runtime.rete.util.Options; + +/** + * A timely communication group implementation. {@link TimelyMailbox}es and {@link LazyFoldingNode}s are ordered in the + * increasing order of timestamps. + * + * @author Tamas Szabo + * @since 2.3 + */ +public class TimelyCommunicationGroup extends CommunicationGroup { + + private final boolean isSingleton; + private final TreeMap> mailboxQueue; + // may be null - only used in the scattered case where we need to take care of mailboxes and resumables too + private Comparator nodeComparator; + private boolean currentlyDelivering; + private Timestamp currentlyDeliveredTimestamp; + + public TimelyCommunicationGroup(final TimelyCommunicationTracker tracker, final Node representative, + final int identifier, final boolean isSingleton) { + super(tracker, representative, identifier); + this.isSingleton = isSingleton; + this.mailboxQueue = CollectionsFactory.createTreeMap(); + this.currentlyDelivering = false; + } + + /** + * Sets the {@link Comparator} to be used to order the {@link Mailbox}es at a given {@link Timestamp} in the mailbox + * queue. Additionally, reorders already queued {@link Mailbox}es to reflect the new comparator. The comparator may + * be null, in this case, no set ordering will be enforced among the {@link Mailbox}es. + */ + public void setComparatorAndReorderMailboxes(final Comparator nodeComparator) { + this.nodeComparator = nodeComparator; + if (!this.mailboxQueue.isEmpty()) { + final HashMap> queueCopy = new HashMap>(this.mailboxQueue); + this.mailboxQueue.clear(); + for (final Entry> entry : queueCopy.entrySet()) { + for (final Mailbox mailbox : entry.getValue()) { + this.notifyHasMessage(mailbox, entry.getKey()); + } + } + } + } + + @Override + public void deliverMessages() { + this.currentlyDelivering = true; + while (!this.mailboxQueue.isEmpty()) { + // care must be taken here how we iterate over the mailboxes + // it is not okay to loop over the mailboxes at once because a mailbox may disappear from the collection as + // a result of delivering messages from another mailboxes under the same timestamp + // because of this, it is crucial that we pick the mailboxes one by one + final Entry> entry = this.mailboxQueue.firstEntry(); + final Timestamp timestamp = entry.getKey(); + final Set mailboxes = entry.getValue(); + final Mailbox mailbox = mailboxes.iterator().next(); + mailboxes.remove(mailbox); + if (mailboxes.isEmpty()) { + this.mailboxQueue.pollFirstEntry(); + } + assert mailbox instanceof TimelyMailbox; + /* debug */ this.currentlyDeliveredTimestamp = timestamp; + mailbox.deliverAll(timestamp); + /* debug */ this.currentlyDeliveredTimestamp = null; + } + this.currentlyDelivering = false; + } + + @Override + public boolean isEmpty() { + return this.mailboxQueue.isEmpty(); + } + + @Override + public void notifyHasMessage(final Mailbox mailbox, MessageSelector kind) { + if (kind instanceof Timestamp) { + final Timestamp timestamp = (Timestamp) kind; + if (Options.MONITOR_VIOLATION_OF_DIFFERENTIAL_DATAFLOW_TIMESTAMPS) { + if (timestamp.compareTo(this.currentlyDeliveredTimestamp) < 0) { + final Logger logger = this.representative.getContainer().getNetwork().getEngine().getLogger(); + logger.error( + "[INTERNAL ERROR] Violation of differential dataflow communication schema! The communication component with representative " + + this.representative + " observed decreasing timestamp during message delivery!"); + } + } + final Set mailboxes = this.mailboxQueue.computeIfAbsent(timestamp, k -> { + if (this.nodeComparator == null) { + return CollectionsFactory.createSet(); + } else { + return new TreeSet(new Comparator() { + @Override + public int compare(final Mailbox left, final Mailbox right) { + return nodeComparator.compare(left.getReceiver(), right.getReceiver()); + } + }); + } + }); + mailboxes.add(mailbox); + if (!this.isEnqueued && !this.currentlyDelivering) { + this.tracker.activateUnenqueued(this); + } + } else { + throw new IllegalArgumentException(UNSUPPORTED_MESSAGE_KIND + kind); + } + } + + @Override + public void notifyLostAllMessages(final Mailbox mailbox, final MessageSelector kind) { + if (kind instanceof Timestamp) { + final Timestamp timestamp = (Timestamp) kind; + this.mailboxQueue.compute(timestamp, (k, v) -> { + if (v == null) { + throw new IllegalStateException("No mailboxes registered at timestamp " + timestamp + "!"); + } + if (!v.remove(mailbox)) { + throw new IllegalStateException( + "The mailbox " + mailbox + " was not registered at timestamp " + timestamp + "!"); + } + if (v.isEmpty()) { + return null; + } else { + return v; + } + }); + if (this.mailboxQueue.isEmpty()) { + this.tracker.deactivate(this); + } + } else { + throw new IllegalArgumentException(UNSUPPORTED_MESSAGE_KIND + kind); + } + } + + @Override + public Map> getMailboxes() { + return Collections.unmodifiableMap(this.mailboxQueue); + } + + @Override + public boolean isRecursive() { + return !this.isSingleton; + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimelyCommunicationTracker.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimelyCommunicationTracker.java new file mode 100644 index 00000000..1ff69882 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimelyCommunicationTracker.java @@ -0,0 +1,216 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.communication.timely; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.function.Function; + +import tools.refinery.viatra.runtime.base.itc.alg.misc.topsort.TopologicalSorting; +import tools.refinery.viatra.runtime.base.itc.graphimpl.Graph; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.rete.index.IndexerListener; +import tools.refinery.viatra.runtime.rete.index.SpecializedProjectionIndexer; +import tools.refinery.viatra.runtime.rete.index.SpecializedProjectionIndexer.ListenerSubscription; +import tools.refinery.viatra.runtime.rete.index.StandardIndexer; +import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration; +import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration.TimelineRepresentation; +import tools.refinery.viatra.runtime.rete.network.NetworkStructureChangeSensitiveNode; +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.ProductionNode; +import tools.refinery.viatra.runtime.rete.network.StandardNode; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker; +import tools.refinery.viatra.runtime.rete.network.communication.MessageSelector; +import tools.refinery.viatra.runtime.rete.network.communication.NodeComparator; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; +import tools.refinery.viatra.runtime.rete.single.DiscriminatorDispatcherNode; + +/** + * Timely (DDF) implementation of the {@link CommunicationTracker}. + * + * @author Tamas Szabo + * @since 2.3 + */ +public class TimelyCommunicationTracker extends CommunicationTracker { + + protected final TimelyConfiguration configuration; + + public TimelyCommunicationTracker(final TimelyConfiguration configuration) { + this.configuration = configuration; + } + + @Override + protected CommunicationGroup createGroup(final Node representative, final int index) { + final boolean isSingleton = this.sccInformationProvider.sccs.getPartition(representative).size() == 1; + return new TimelyCommunicationGroup(this, representative, index, isSingleton); + } + + @Override + protected void reconstructQueueContents(final Set oldActiveGroups) { + for (final CommunicationGroup oldGroup : oldActiveGroups) { + for (final Entry> entry : oldGroup.getMailboxes().entrySet()) { + for (final Mailbox mailbox : entry.getValue()) { + final CommunicationGroup newGroup = this.groupMap.get(mailbox.getReceiver()); + newGroup.notifyHasMessage(mailbox, entry.getKey()); + } + } + } + } + + @Override + public Mailbox proxifyMailbox(final Node requester, final Mailbox original) { + final Mailbox mailboxToProxify = (original instanceof TimelyMailboxProxy) + ? ((TimelyMailboxProxy) original).getWrappedMailbox() + : original; + final TimestampTransformation preprocessor = getPreprocessor(requester, mailboxToProxify.getReceiver()); + if (preprocessor == null) { + return mailboxToProxify; + } else { + return new TimelyMailboxProxy(mailboxToProxify, preprocessor); + } + } + + @Override + public IndexerListener proxifyIndexerListener(final Node requester, final IndexerListener original) { + final IndexerListener listenerToProxify = (original instanceof TimelyIndexerListenerProxy) + ? ((TimelyIndexerListenerProxy) original).getWrappedIndexerListener() + : original; + final TimestampTransformation preprocessor = getPreprocessor(requester, listenerToProxify.getOwner()); + if (preprocessor == null) { + return listenerToProxify; + } else { + return new TimelyIndexerListenerProxy(listenerToProxify, preprocessor); + } + } + + protected TimestampTransformation getPreprocessor(final Node source, final Node target) { + final Node effectiveSource = source instanceof SpecializedProjectionIndexer + ? ((SpecializedProjectionIndexer) source).getActiveNode() + : source; + final CommunicationGroup sourceGroup = this.getGroup(effectiveSource); + final CommunicationGroup targetGroup = this.getGroup(target); + + if (sourceGroup != null && targetGroup != null) { + // during RETE construction, the groups may be still null + if (sourceGroup != targetGroup && sourceGroup.isRecursive()) { + // targetGroup is a successor SCC of sourceGroup + // and sourceGroup is a recursive SCC + // then we need to zero out the timestamps + return TimestampTransformation.RESET; + } + if (sourceGroup == targetGroup && target instanceof ProductionNode) { + // if requester and receiver are in the same SCC + // and receiver is a production node + // then we need to increment the timestamps + return TimestampTransformation.INCREMENT; + } + } + + return null; + } + + @Override + protected void postProcessNode(final Node node) { + if (node instanceof NetworkStructureChangeSensitiveNode) { + ((NetworkStructureChangeSensitiveNode) node).networkStructureChanged(); + } + } + + @Override + protected void postProcessGroup(final CommunicationGroup group) { + if (this.configuration.getTimelineRepresentation() == TimelineRepresentation.FAITHFUL) { + final Node representative = group.getRepresentative(); + final Set groupMembers = this.sccInformationProvider.sccs.getPartition(representative); + if (groupMembers.size() > 1) { + final Graph graph = new Graph(); + + for (final Node node : groupMembers) { + graph.insertNode(node); + } + + for (final Node source : groupMembers) { + for (final Node target : this.dependencyGraph.getTargetNodes(source)) { + // (1) the edge is not a recursion cut point + // (2) the edge is within this group + if (!this.isRecursionCutPoint(source, target) && groupMembers.contains(target)) { + graph.insertEdge(source, target); + } + } + } + + final List orderedNodes = TopologicalSorting.compute(graph); + final Map nodeMap = CollectionsFactory.createMap(); + int identifier = 0; + for (final Node orderedNode : orderedNodes) { + nodeMap.put(orderedNode, identifier++); + } + + ((TimelyCommunicationGroup) group).setComparatorAndReorderMailboxes(new NodeComparator(nodeMap)); + } + } + } + + /** + * This static field is used for debug purposes in the DotGenerator. + */ + public static final Function> EDGE_LABEL_FUNCTION = new Function>() { + + @Override + public Function apply(final Node source) { + return new Function() { + @Override + public String apply(final Node target) { + if (source instanceof SpecializedProjectionIndexer) { + final Collection subscriptions = ((SpecializedProjectionIndexer) source) + .getSubscriptions(); + for (final ListenerSubscription subscription : subscriptions) { + if (subscription.getListener().getOwner() == target + && subscription.getListener() instanceof TimelyIndexerListenerProxy) { + return ((TimelyIndexerListenerProxy) subscription.getListener()).preprocessor + .toString(); + } + } + } + if (source instanceof StandardIndexer) { + final Collection listeners = ((StandardIndexer) source).getListeners(); + for (final IndexerListener listener : listeners) { + if (listener.getOwner() == target && listener instanceof TimelyIndexerListenerProxy) { + return ((TimelyIndexerListenerProxy) listener).preprocessor.toString(); + } + } + } + if (source instanceof StandardNode) { + final Collection mailboxes = ((StandardNode) source).getChildMailboxes(); + for (final Mailbox mailbox : mailboxes) { + if (mailbox.getReceiver() == target && mailbox instanceof TimelyMailboxProxy) { + return ((TimelyMailboxProxy) mailbox).preprocessor.toString(); + } + } + } + if (source instanceof DiscriminatorDispatcherNode) { + final Collection mailboxes = ((DiscriminatorDispatcherNode) source) + .getBucketMailboxes().values(); + for (final Mailbox mailbox : mailboxes) { + if (mailbox.getReceiver() == target && mailbox instanceof TimelyMailboxProxy) { + return ((TimelyMailboxProxy) mailbox).preprocessor.toString(); + } + } + } + return null; + } + }; + } + + }; + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimelyIndexerListenerProxy.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimelyIndexerListenerProxy.java new file mode 100644 index 00000000..e8fbf84e --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimelyIndexerListenerProxy.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.communication.timely; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; +import tools.refinery.viatra.runtime.rete.index.IndexerListener; +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.ProductionNode; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * A timely proxy for another {@link IndexerListener}, which performs some preprocessing + * on the differential timestamps before passing it on to the real recipient. + *

    + * These proxies are used on edges leading into {@link ProductionNode}s. Because {@link ProductionNode}s + * never ask back the indexer for its contents, there is no need to also apply the proxy on that direction. + * + * @author Tamas Szabo + * @since 2.3 + */ +public class TimelyIndexerListenerProxy implements IndexerListener { + + protected final TimestampTransformation preprocessor; + protected final IndexerListener wrapped; + + public TimelyIndexerListenerProxy(final IndexerListener wrapped, + final TimestampTransformation preprocessor) { + Preconditions.checkArgument(!(wrapped instanceof TimelyIndexerListenerProxy), "Proxy in a proxy is not allowed!"); + this.wrapped = wrapped; + this.preprocessor = preprocessor; + } + + public IndexerListener getWrappedIndexerListener() { + return wrapped; + } + + @Override + public Node getOwner() { + return this.wrapped.getOwner(); + } + + @Override + public void notifyIndexerUpdate(final Direction direction, final Tuple updateElement, final Tuple signature, + final boolean change, final Timestamp timestamp) { + this.wrapped.notifyIndexerUpdate(direction, updateElement, signature, change, preprocessor.process(timestamp)); + } + + @Override + public String toString() { + return this.preprocessor.toString() + "_PROXY -> " + this.wrapped.toString(); + } + + @Override + public boolean equals(final Object obj) { + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } else if (obj == this) { + return true; + } else { + final TimelyIndexerListenerProxy that = (TimelyIndexerListenerProxy) obj; + return this.wrapped.equals(that.wrapped) && this.preprocessor == that.preprocessor; + } + } + + @Override + public int hashCode() { + int hash = 1; + hash = hash * 17 + this.wrapped.hashCode(); + hash = hash * 31 + this.preprocessor.hashCode(); + return hash; + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimelyMailboxProxy.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimelyMailboxProxy.java new file mode 100644 index 00000000..550bfbeb --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimelyMailboxProxy.java @@ -0,0 +1,102 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.communication.timely; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.MessageSelector; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; + +/** + * A timely proxy for another {@link Mailbox}, which performs some preprocessing + * on the differential timestamps before passing it on to the real recipient. + * + * @author Tamas Szabo + * @since 2.3 + */ +public class TimelyMailboxProxy implements Mailbox { + + protected final TimestampTransformation preprocessor; + protected final Mailbox wrapped; + + public TimelyMailboxProxy(final Mailbox wrapped, final TimestampTransformation preprocessor) { + Preconditions.checkArgument(!(wrapped instanceof TimelyMailboxProxy), "Proxy in a proxy is not allowed!"); + this.wrapped = wrapped; + this.preprocessor = preprocessor; + } + + public Mailbox getWrappedMailbox() { + return wrapped; + } + + @Override + public void postMessage(final Direction direction, final Tuple update, final Timestamp timestamp) { + this.wrapped.postMessage(direction, update, preprocessor.process(timestamp)); + } + + @Override + public String toString() { + return this.preprocessor.toString() + "_PROXY -> " + this.wrapped.toString(); + } + + @Override + public void clear() { + this.wrapped.clear(); + } + + @Override + public void deliverAll(final MessageSelector selector) { + this.wrapped.deliverAll(selector); + } + + @Override + public CommunicationGroup getCurrentGroup() { + return this.wrapped.getCurrentGroup(); + } + + @Override + public void setCurrentGroup(final CommunicationGroup group) { + this.wrapped.setCurrentGroup(group); + } + + @Override + public Receiver getReceiver() { + return this.wrapped.getReceiver(); + } + + @Override + public boolean isEmpty() { + return this.wrapped.isEmpty(); + } + + @Override + public boolean equals(final Object obj) { + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } else if (obj == this) { + return true; + } else { + final TimelyMailboxProxy that = (TimelyMailboxProxy) obj; + return this.wrapped.equals(that.wrapped) && this.preprocessor == that.preprocessor; + } + } + + @Override + public int hashCode() { + int hash = 1; + hash = hash * 17 + this.wrapped.hashCode(); + hash = hash * 31 + this.preprocessor.hashCode(); + return hash; + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimestampTransformation.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimestampTransformation.java new file mode 100644 index 00000000..8929eb5c --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/communication/timely/TimestampTransformation.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.communication.timely; + +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * Values of this enum perform different kind of preprocessing on {@link Timestamp}s. + * This is used on edges leading in and out from {@link Node}s in recursive {@link TimelyCommunicationGroup}s. + * + * @author Tamas Szabo + * @since 2.3 + */ +public enum TimestampTransformation { + + INCREMENT { + @Override + public Timestamp process(final Timestamp timestamp) { + return new Timestamp(timestamp.getValue() + 1); + } + + @Override + public String toString() { + return "INCREMENT"; + } + }, + RESET { + @Override + public Timestamp process(final Timestamp timestamp) { + return Timestamp.ZERO; + } + + @Override + public String toString() { + return "RESET"; + } + }; + + public abstract Timestamp process(final Timestamp timestamp); + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/delayed/DelayedCommand.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/delayed/DelayedCommand.java new file mode 100644 index 00000000..d6312671 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/delayed/DelayedCommand.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.delayed; + +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.Signed; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.Network; +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.Supplier; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; + +/** + * Instances of this class are responsible for initializing a {@link Receiver} with the contents of a {@link Supplier}. + * However, due to the dynamic nature of the Rete {@link Network} and to the fact that certain {@link Node}s in the + * {@link Network} are sensitive to the shape of the {@link Network}, the commands must be delayed until the + * construction of the {@link Network} has stabilized. + * + * @author Tamas Szabo + * @since 2.3 + */ +public abstract class DelayedCommand implements Runnable { + + protected final Supplier supplier; + protected final Receiver receiver; + protected final Direction direction; + protected final ReteContainer container; + + public DelayedCommand(final Supplier supplier, final Receiver receiver, final Direction direction, + final ReteContainer container) { + this.supplier = supplier; + this.receiver = receiver; + this.direction = direction; + this.container = container; + } + + @Override + public void run() { + final CommunicationTracker tracker = this.container.getCommunicationTracker(); + final Mailbox mailbox = tracker.proxifyMailbox(this.supplier, this.receiver.getMailbox()); + + if (this.isTimestampAware()) { + final Map> contents = this.container.pullContentsWithTimeline(this.supplier, + false); + for (final Entry> entry : contents.entrySet()) { + for (final Signed change : entry.getValue().asChangeSequence()) { + mailbox.postMessage(change.getDirection().multiply(this.direction), entry.getKey(), + change.getPayload()); + } + } + } else { + final Collection contents = this.container.pullContents(this.supplier, false); + for (final Tuple tuple : contents) { + mailbox.postMessage(this.direction, tuple, Timestamp.ZERO); + } + } + } + + @Override + public String toString() { + return this.supplier + " -> " + this.receiver.toString(); + } + + protected abstract boolean isTimestampAware(); + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/delayed/DelayedConnectCommand.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/delayed/DelayedConnectCommand.java new file mode 100644 index 00000000..1bfdbec6 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/delayed/DelayedConnectCommand.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.delayed; + +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.Supplier; + +public class DelayedConnectCommand extends DelayedCommand { + + public DelayedConnectCommand(final Supplier supplier, final Receiver receiver, final ReteContainer container) { + super(supplier, receiver, Direction.INSERT, container); + } + + @Override + protected boolean isTimestampAware() { + return this.container.isTimelyEvaluation() && this.container.getCommunicationTracker().areInSameGroup(this.supplier, this.receiver); + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/delayed/DelayedDisconnectCommand.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/delayed/DelayedDisconnectCommand.java new file mode 100644 index 00000000..5825a971 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/delayed/DelayedDisconnectCommand.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.delayed; + +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.Supplier; + +public class DelayedDisconnectCommand extends DelayedCommand { + + protected final boolean wasInSameSCC; + + public DelayedDisconnectCommand(final Supplier supplier, final Receiver receiver, final ReteContainer container, final boolean wasInSameSCC) { + super(supplier, receiver, Direction.DELETE, container); + this.wasInSameSCC = wasInSameSCC; + } + + @Override + protected boolean isTimestampAware() { + return this.wasInSameSCC; + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/indexer/DefaultMessageIndexer.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/indexer/DefaultMessageIndexer.java new file mode 100644 index 00000000..da9bc47e --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/indexer/DefaultMessageIndexer.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.indexer; + +import java.util.Collections; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; + +/** + * @author Tamas Szabo + * @since 2.0 + */ +public class DefaultMessageIndexer implements MessageIndexer { + + protected final Map indexer; + + public DefaultMessageIndexer() { + this.indexer = CollectionsFactory.createMap(); + } + + public Map getTuples() { + return Collections.unmodifiableMap(this.indexer); + } + + @Override + public int getCount(final Tuple update) { + final Integer count = getTuples().get(update); + if (count == null) { + return 0; + } else { + return count; + } + } + + @Override + public void insert(final Tuple update) { + update(update, 1); + } + + @Override + public void delete(final Tuple update) { + update(update, -1); + } + + @Override + public void update(final Tuple update, final int delta) { + final Integer oldCount = this.indexer.get(update); + final int newCount = (oldCount == null ? 0 : oldCount) + delta; + if (newCount == 0) { + this.indexer.remove(update); + } else { + this.indexer.put(update, newCount); + } + } + + @Override + public boolean isEmpty() { + return this.indexer.isEmpty(); + } + + @Override + public void clear() { + this.indexer.clear(); + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/indexer/GroupBasedMessageIndexer.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/indexer/GroupBasedMessageIndexer.java new file mode 100644 index 00000000..80271252 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/indexer/GroupBasedMessageIndexer.java @@ -0,0 +1,95 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.indexer; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; + +/** + * @author Tamas Szabo + * @since 2.0 + */ +public class GroupBasedMessageIndexer implements MessageIndexer { + + protected final Map indexer; + protected final TupleMask groupMask; + + public GroupBasedMessageIndexer(final TupleMask groupMask) { + this.indexer = CollectionsFactory.createMap(); + this.groupMask = groupMask; + } + + public Map getTuplesByGroup(final Tuple group) { + final DefaultMessageIndexer values = this.indexer.get(group); + if (values == null) { + return Collections.emptyMap(); + } else { + return Collections.unmodifiableMap(values.getTuples()); + } + } + + @Override + public int getCount(final Tuple update) { + final Tuple group = this.groupMask.transform(update); + final Integer count = getTuplesByGroup(group).get(update); + if (count == null) { + return 0; + } else { + return count; + } + } + + public Set getGroups() { + return Collections.unmodifiableSet(this.indexer.keySet()); + } + + @Override + public void insert(final Tuple update) { + update(update, 1); + } + + @Override + public void delete(final Tuple update) { + update(update, -1); + } + + @Override + public void update(final Tuple update, final int delta) { + final Tuple group = this.groupMask.transform(update); + DefaultMessageIndexer valueIndexer = this.indexer.get(group); + + if (valueIndexer == null) { + valueIndexer = new DefaultMessageIndexer(); + this.indexer.put(group, valueIndexer); + } + + valueIndexer.update(update, delta); + + // it may happen that the indexer becomes empty as a result of the update + if (valueIndexer.isEmpty()) { + this.indexer.remove(group); + } + } + + @Override + public boolean isEmpty() { + return this.indexer.isEmpty(); + } + + @Override + public void clear() { + this.indexer.clear(); + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/indexer/MessageIndexer.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/indexer/MessageIndexer.java new file mode 100644 index 00000000..271aaa44 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/indexer/MessageIndexer.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.indexer; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Clearable; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; + +/** + * A message indexer is used by {@link Mailbox}es to index their contents. + * + * @author Tamas Szabo + * @since 2.0 + */ +public interface MessageIndexer extends Clearable { + + public void insert(final Tuple update); + + public void delete(final Tuple update); + + public void update(final Tuple update, final int delta); + + public boolean isEmpty(); + + public int getCount(final Tuple update); + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/AdaptableMailbox.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/AdaptableMailbox.java new file mode 100644 index 00000000..99097f56 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/AdaptableMailbox.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.mailbox; + +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker; +import tools.refinery.viatra.runtime.rete.network.communication.timely.TimelyMailboxProxy; +import tools.refinery.viatra.runtime.rete.network.mailbox.timeless.BehaviorChangingMailbox; + +/** + * An adaptable mailbox can be wrapped by another mailbox to act in behalf of that. The significance of the adaptation + * is that the adaptee will notify the {@link CommunicationTracker} about updates by promoting the adapter itself. + * Adaptable mailboxes are used by the {@link BehaviorChangingMailbox}. + * + * Compare this with {@link TimelyMailboxProxy}. That one also wraps another mailbox in order to + * perform preprocessing on the messages sent to the original recipient. + * + * @author Tamas Szabo + * @since 2.0 + */ +public interface AdaptableMailbox extends Mailbox { + + public Mailbox getAdapter(); + + public void setAdapter(final Mailbox adapter); + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/FallThroughCapableMailbox.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/FallThroughCapableMailbox.java new file mode 100644 index 00000000..8797e254 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/FallThroughCapableMailbox.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.mailbox; + +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker; + +/** + * A fall through capable mailbox can directly call the update method of its {@link Receiver} instead of using the + * standard post-deliver mailbox semantics. If the fall through flag is set to true, the mailbox uses direct delivery, + * otherwise it operates in the original behavior. The fall through operation is preferable whenever applicable because + * it improves performance. The fall through flag is controlled by the {@link CommunicationTracker} based on the + * receiver node type and network topology. + * + * @author Tamas Szabo + * @since 2.2 + */ +public interface FallThroughCapableMailbox extends Mailbox { + + public boolean isFallThrough(); + + public void setFallThrough(final boolean fallThrough); + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/Mailbox.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/Mailbox.java new file mode 100644 index 00000000..05005974 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/Mailbox.java @@ -0,0 +1,78 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.mailbox; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Clearable; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.IGroupable; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.MessageSelector; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * A mailbox is associated with every {@link Receiver}. Messages can be sent to a {@link Receiver} by posting them into + * the mailbox. Different mailbox implementations may differ in the way how they deliver the posted messages. + * + * @author Tamas Szabo + * @since 2.0 + * + */ +public interface Mailbox extends Clearable, IGroupable { + + /** + * Posts a new message to this mailbox. + * + * @param direction + * the direction of the update + * @param update + * the update element + * @since 2.4 + */ + public void postMessage(final Direction direction, final Tuple update, final Timestamp timestamp); + + /** + * Delivers all messages according to the given selector from this mailbox. The selector can also be null. In this case, no + * special separation is expected between the messages. + * + * @param selector the message selector + */ + public void deliverAll(final MessageSelector selector); + + /** + * Returns the {@link Receiver} of this mailbox. + * + * @return the receiver + */ + public Receiver getReceiver(); + + /** + * Returns the {@link CommunicationGroup} of the receiver of this mailbox. + * + * @return the communication group + */ + public CommunicationGroup getCurrentGroup(); + + /** + * Sets the {@link CommunicationGroup} that the receiver of this mailbox is associated with. + * + * @param group + * the communication group + */ + public void setCurrentGroup(final CommunicationGroup group); + + /** + * Returns true if this mailbox is empty. + * + * @return + */ + public boolean isEmpty(); + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/MessageIndexerFactory.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/MessageIndexerFactory.java new file mode 100644 index 00000000..2c5255fb --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/MessageIndexerFactory.java @@ -0,0 +1,23 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.mailbox; + +import tools.refinery.viatra.runtime.rete.network.indexer.MessageIndexer; + +/** + * A factory used to create message indexers for {@link Mailbox}es. + * + * @author Tamas Szabo + * @since 2.0 + */ +public interface MessageIndexerFactory { + + public I create(); + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/AbstractUpdateSplittingMailbox.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/AbstractUpdateSplittingMailbox.java new file mode 100644 index 00000000..1e1ada71 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/AbstractUpdateSplittingMailbox.java @@ -0,0 +1,109 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.mailbox.timeless; + +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.indexer.MessageIndexer; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.MessageIndexerFactory; + +/** + * An abstract mailbox implementation that is capable of splitting update messages based on some form of monotonicity + * (anti-monotone and monotone). The monotonicity is either defined by the less or equal operator of a poset or, it can + * be the standard subset ordering among sets of tuples. + * + * @author Tamas Szabo + * @since 2.0 + * + */ +public abstract class AbstractUpdateSplittingMailbox implements Mailbox { + + protected IndexerType monotoneQueue; + protected IndexerType antiMonotoneQueue; + protected IndexerType monotoneBuffer; + protected IndexerType antiMonotoneBuffer; + protected boolean deliveringMonotone; + protected boolean deliveringAntiMonotone; + protected final ReceiverType receiver; + protected final ReteContainer container; + protected CommunicationGroup group; + + public AbstractUpdateSplittingMailbox(final ReceiverType receiver, final ReteContainer container, + final MessageIndexerFactory factory) { + this.receiver = receiver; + this.container = container; + this.monotoneQueue = factory.create(); + this.antiMonotoneQueue = factory.create(); + this.monotoneBuffer = factory.create(); + this.antiMonotoneBuffer = factory.create(); + this.deliveringMonotone = false; + this.deliveringAntiMonotone = false; + } + + protected void swapAndClearMonotone() { + final IndexerType tmp = this.monotoneQueue; + this.monotoneQueue = this.monotoneBuffer; + this.monotoneBuffer = tmp; + this.monotoneBuffer.clear(); + } + + protected void swapAndClearAntiMonotone() { + final IndexerType tmp = this.antiMonotoneQueue; + this.antiMonotoneQueue = this.antiMonotoneBuffer; + this.antiMonotoneBuffer = tmp; + this.antiMonotoneBuffer.clear(); + } + + protected IndexerType getActiveMonotoneQueue() { + if (this.deliveringMonotone) { + return this.monotoneBuffer; + } else { + return this.monotoneQueue; + } + } + + protected IndexerType getActiveAntiMonotoneQueue() { + if (this.deliveringAntiMonotone) { + return this.antiMonotoneBuffer; + } else { + return this.antiMonotoneQueue; + } + } + + @Override + public ReceiverType getReceiver() { + return this.receiver; + } + + @Override + public void clear() { + this.monotoneQueue.clear(); + this.antiMonotoneQueue.clear(); + this.monotoneBuffer.clear(); + this.antiMonotoneBuffer.clear(); + } + + @Override + public boolean isEmpty() { + return this.getActiveMonotoneQueue().isEmpty() && this.getActiveAntiMonotoneQueue().isEmpty(); + } + + @Override + public CommunicationGroup getCurrentGroup() { + return this.group; + } + + @Override + public void setCurrentGroup(final CommunicationGroup group) { + this.group = group; + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/BehaviorChangingMailbox.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/BehaviorChangingMailbox.java new file mode 100644 index 00000000..fe822d7c --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/BehaviorChangingMailbox.java @@ -0,0 +1,117 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.mailbox.timeless; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker; +import tools.refinery.viatra.runtime.rete.network.communication.MessageSelector; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.communication.timeless.TimelessCommunicationTracker; +import tools.refinery.viatra.runtime.rete.network.mailbox.AdaptableMailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.FallThroughCapableMailbox; + +/** + * This mailbox changes its behavior based on the position of its {@link Receiver} in the network topology. + * It either behaves as a {@link DefaultMailbox} or as an {@link UpdateSplittingMailbox}. The decision is made by the + * {@link CommunicationTracker}, see {@link TimelessCommunicationTracker#postProcessNode(Node)} for more details. + * + * @author Tamas Szabo + */ +public class BehaviorChangingMailbox implements FallThroughCapableMailbox { + + protected boolean fallThrough; + protected boolean split; + protected AdaptableMailbox wrapped; + protected final Receiver receiver; + protected final ReteContainer container; + protected CommunicationGroup group; + + public BehaviorChangingMailbox(final Receiver receiver, final ReteContainer container) { + this.fallThrough = false; + this.split = false; + this.receiver = receiver; + this.container = container; + this.wrapped = new DefaultMailbox(receiver, container); + this.wrapped.setAdapter(this); + } + + @Override + public void postMessage(final Direction direction, final Tuple update, final Timestamp timestamp) { + if (this.fallThrough && !this.container.isExecutingDelayedCommands()) { + // disable fall through while we are in the middle of executing delayed construction commands + this.receiver.update(direction, update, timestamp); + } else { + this.wrapped.postMessage(direction, update, timestamp); + } + } + + @Override + public void deliverAll(final MessageSelector kind) { + this.wrapped.deliverAll(kind); + } + + @Override + public String toString() { + return "A_MBOX -> " + this.wrapped; + } + + public void setSplitFlag(final boolean splitValue) { + if (this.split != splitValue) { + assert isEmpty(); + if (splitValue) { + this.wrapped = new UpdateSplittingMailbox(this.receiver, this.container); + } else { + this.wrapped = new DefaultMailbox(this.receiver, this.container); + } + this.wrapped.setAdapter(this); + this.split = splitValue; + } + } + + @Override + public boolean isEmpty() { + return this.wrapped.isEmpty(); + } + + @Override + public void clear() { + this.wrapped.clear(); + } + + @Override + public Receiver getReceiver() { + return this.receiver; + } + + @Override + public CommunicationGroup getCurrentGroup() { + return this.group; + } + + @Override + public void setCurrentGroup(final CommunicationGroup group) { + this.group = group; + } + + @Override + public boolean isFallThrough() { + return this.fallThrough; + } + + @Override + public void setFallThrough(final boolean fallThrough) { + this.fallThrough = fallThrough; + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/DefaultMailbox.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/DefaultMailbox.java new file mode 100644 index 00000000..5c72ba39 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/DefaultMailbox.java @@ -0,0 +1,164 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.mailbox.timeless; + +import java.util.Map; +import java.util.Map.Entry; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.MessageSelector; +import tools.refinery.viatra.runtime.rete.network.communication.PhasedSelector; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.mailbox.AdaptableMailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; + +/** + * Default mailbox implementation. + *

    + * Usually, the mailbox performs counting of messages so that they can cancel each other out. However, if marked as a + * fall-through mailbox, than update messages are delivered directly to the receiver node to reduce overhead. + * + * @author Tamas Szabo + * @since 2.0 + */ +public class DefaultMailbox implements AdaptableMailbox { + + private static int SIZE_TRESHOLD = 127; + + protected Map queue; + protected Map buffer; + protected final Receiver receiver; + protected final ReteContainer container; + protected boolean delivering; + protected Mailbox adapter; + protected CommunicationGroup group; + + public DefaultMailbox(final Receiver receiver, final ReteContainer container) { + this.receiver = receiver; + this.container = container; + this.queue = CollectionsFactory.createMap(); + this.buffer = CollectionsFactory.createMap(); + this.adapter = this; + } + + protected Map getActiveQueue() { + if (this.delivering) { + return this.buffer; + } else { + return this.queue; + } + } + + @Override + public Mailbox getAdapter() { + return this.adapter; + } + + @Override + public void setAdapter(final Mailbox adapter) { + this.adapter = adapter; + } + + @Override + public boolean isEmpty() { + return getActiveQueue().isEmpty(); + } + + @Override + public void postMessage(final Direction direction, final Tuple update, final Timestamp timestamp) { + final Map activeQueue = getActiveQueue(); + final boolean wasEmpty = activeQueue.isEmpty(); + + boolean significantChange = false; + Integer count = activeQueue.get(update); + if (count == null) { + count = 0; + significantChange = true; + } + + if (direction == Direction.DELETE) { + count--; + } else { + count++; + } + + if (count == 0) { + activeQueue.remove(update); + significantChange = true; + } else { + activeQueue.put(update, count); + } + + if (significantChange) { + final Mailbox targetMailbox = this.adapter; + final CommunicationGroup targetGroup = this.adapter.getCurrentGroup(); + + if (wasEmpty) { + targetGroup.notifyHasMessage(targetMailbox, PhasedSelector.DEFAULT); + } else if (activeQueue.isEmpty()) { + targetGroup.notifyLostAllMessages(targetMailbox, PhasedSelector.DEFAULT); + } + } + } + + @Override + public void deliverAll(final MessageSelector kind) { + if (kind == PhasedSelector.DEFAULT) { + // use the buffer during delivering so that there is a clear + // separation between the stages + this.delivering = true; + this.receiver.batchUpdate(this.queue.entrySet(), Timestamp.ZERO); + this.delivering = false; + + if (queue.size() > SIZE_TRESHOLD) { + this.queue = this.buffer; + this.buffer = CollectionsFactory.createMap(); + } else { + this.queue.clear(); + final Map tmpQueue = this.queue; + this.queue = this.buffer; + this.buffer = tmpQueue; + } + } else { + throw new IllegalArgumentException("Unsupported message kind " + kind); + } + } + + @Override + public String toString() { + return "D_MBOX (" + this.receiver + ") " + this.getActiveQueue(); + } + + @Override + public Receiver getReceiver() { + return this.receiver; + } + + @Override + public void clear() { + this.queue.clear(); + this.buffer.clear(); + } + + @Override + public CommunicationGroup getCurrentGroup() { + return this.group; + } + + @Override + public void setCurrentGroup(final CommunicationGroup group) { + this.group = group; + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/PosetAwareMailbox.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/PosetAwareMailbox.java new file mode 100644 index 00000000..50d19882 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/PosetAwareMailbox.java @@ -0,0 +1,218 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.mailbox.timeless; + +import java.util.HashSet; +import java.util.Map.Entry; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.context.IPosetComparator; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.PosetAwareReceiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.MessageSelector; +import tools.refinery.viatra.runtime.rete.network.communication.PhasedSelector; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.indexer.GroupBasedMessageIndexer; + +/** + * A monotonicity aware mailbox implementation. The mailbox uses an {@link IPosetComparator} to identify if a pair of + * REVOKE - INSERT updates represent a monotone change pair. The mailbox is used by {@link PosetAwareReceiver}s. + * + * @author Tamas Szabo + * @since 2.0 + */ +public class PosetAwareMailbox extends AbstractUpdateSplittingMailbox { + + protected final TupleMask groupMask; + + public PosetAwareMailbox(final PosetAwareReceiver receiver, final ReteContainer container) { + super(receiver, container, () -> new GroupBasedMessageIndexer(receiver.getCoreMask())); + this.groupMask = receiver.getCoreMask(); + } + + @Override + public void postMessage(final Direction direction, final Tuple update, final Timestamp timestamp) { + final GroupBasedMessageIndexer monotoneQueue = getActiveMonotoneQueue(); + final GroupBasedMessageIndexer antiMonotoneQueue = getActiveAntiMonotoneQueue(); + final boolean wasPresentAsMonotone = monotoneQueue.getCount(update) != 0; + final boolean wasPresentAsAntiMonotone = antiMonotoneQueue.getCount(update) != 0; + final TupleMask coreMask = this.receiver.getCoreMask(); + + // it cannot happen that it was present in both + assert !(wasPresentAsMonotone && wasPresentAsAntiMonotone); + + if (direction == Direction.INSERT) { + if (wasPresentAsAntiMonotone) { + // it was an anti-monotone one before + antiMonotoneQueue.insert(update); + } else { + // it was a monotone one before or did not exist at all + monotoneQueue.insert(update); + + // if it was not present in the monotone queue before, then + // we need to check whether it makes REVOKE updates monotone + if (!wasPresentAsMonotone) { + final Set counterParts = tryFindCounterPart(update, false, true); + for (final Tuple counterPart : counterParts) { + final int count = antiMonotoneQueue.getCount(counterPart); + assert count < 0; + antiMonotoneQueue.update(counterPart, -count); + monotoneQueue.update(counterPart, count); + } + } + } + } else { + if (wasPresentAsAntiMonotone) { + // it was an anti-monotone one before + antiMonotoneQueue.delete(update); + } else if (wasPresentAsMonotone) { + // it was a monotone one before + monotoneQueue.delete(update); + + // and we need to check whether the monotone REVOKE updates + // still have a reinforcing counterpart + final Set candidates = new HashSet(); + final Tuple key = coreMask.transform(update); + for (final Entry entry : monotoneQueue.getTuplesByGroup(key).entrySet()) { + if (entry.getValue() < 0) { + final Tuple candidate = entry.getKey(); + final Set counterParts = tryFindCounterPart(candidate, true, false); + if (counterParts.isEmpty()) { + // all of them are gone + candidates.add(candidate); + } + } + } + + // move the candidates from the monotone queue to the + // anti-monotone queue because they do not have a + // counterpart anymore + for (final Tuple candidate : candidates) { + final int count = monotoneQueue.getCount(candidate); + assert count < 0; + monotoneQueue.update(candidate, -count); + antiMonotoneQueue.update(candidate, count); + } + } else { + // it did not exist before + final Set counterParts = tryFindCounterPart(update, true, false); + if (counterParts.isEmpty()) { + // there is no tuple that would make this update monotone + antiMonotoneQueue.delete(update); + } else { + // there is a reinforcing counterpart + monotoneQueue.delete(update); + } + } + } + + if (antiMonotoneQueue.isEmpty()) { + this.group.notifyLostAllMessages(this, PhasedSelector.ANTI_MONOTONE); + } else { + this.group.notifyHasMessage(this, PhasedSelector.ANTI_MONOTONE); + } + + if (monotoneQueue.isEmpty()) { + this.group.notifyLostAllMessages(this, PhasedSelector.MONOTONE); + } else { + this.group.notifyHasMessage(this, PhasedSelector.MONOTONE); + } + } + + protected Set tryFindCounterPart(final Tuple first, final boolean findPositiveCounterPart, + final boolean findAllCounterParts) { + final GroupBasedMessageIndexer monotoneQueue = getActiveMonotoneQueue(); + final GroupBasedMessageIndexer antiMonotoneQueue = getActiveAntiMonotoneQueue(); + final TupleMask coreMask = this.receiver.getCoreMask(); + final TupleMask posetMask = this.receiver.getPosetMask(); + final IPosetComparator posetComparator = this.receiver.getPosetComparator(); + final Set result = CollectionsFactory.createSet(); + final Tuple firstKey = coreMask.transform(first); + final Tuple firstValue = posetMask.transform(first); + + if (findPositiveCounterPart) { + for (final Entry entry : monotoneQueue.getTuplesByGroup(firstKey).entrySet()) { + final Tuple secondValue = posetMask.transform(entry.getKey()); + if (entry.getValue() > 0 && posetComparator.isLessOrEqual(firstValue, secondValue)) { + result.add(entry.getKey()); + if (!findAllCounterParts) { + return result; + } + } + } + } else { + for (final Entry entry : antiMonotoneQueue.getTuplesByGroup(firstKey).entrySet()) { + final Tuple secondValue = posetMask.transform(entry.getKey()); + if (posetComparator.isLessOrEqual(secondValue, firstValue)) { + result.add(entry.getKey()); + if (!findAllCounterParts) { + return result; + } + } + } + } + + return result; + } + + @Override + public void deliverAll(final MessageSelector kind) { + if (kind == PhasedSelector.ANTI_MONOTONE) { + // use the buffer during delivering so that there is a clear + // separation between the stages + this.deliveringAntiMonotone = true; + + for (final Tuple group : this.antiMonotoneQueue.getGroups()) { + for (final Entry entry : this.antiMonotoneQueue.getTuplesByGroup(group).entrySet()) { + final Tuple update = entry.getKey(); + final int count = entry.getValue(); + assert count < 0; + for (int i = 0; i < Math.abs(count); i++) { + this.receiver.updateWithPosetInfo(Direction.DELETE, update, false); + } + } + } + + this.deliveringAntiMonotone = false; + swapAndClearAntiMonotone(); + } else if (kind == PhasedSelector.MONOTONE) { + // use the buffer during delivering so that there is a clear + // separation between the stages + this.deliveringMonotone = true; + + for (final Tuple group : this.monotoneQueue.getGroups()) { + for (final Entry entry : this.monotoneQueue.getTuplesByGroup(group).entrySet()) { + final Tuple update = entry.getKey(); + final int count = entry.getValue(); + assert count != 0; + final Direction direction = count < 0 ? Direction.DELETE : Direction.INSERT; + for (int i = 0; i < Math.abs(count); i++) { + this.receiver.updateWithPosetInfo(direction, update, true); + } + } + } + + this.deliveringMonotone = false; + swapAndClearMonotone(); + } else { + throw new IllegalArgumentException("Unsupported message kind " + kind); + } + } + + @Override + public String toString() { + return "PA_MBOX (" + this.receiver + ") " + this.getActiveMonotoneQueue() + " " + + this.getActiveAntiMonotoneQueue(); + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/UpdateSplittingMailbox.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/UpdateSplittingMailbox.java new file mode 100644 index 00000000..afa155b2 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timeless/UpdateSplittingMailbox.java @@ -0,0 +1,135 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.mailbox.timeless; + +import java.util.Map.Entry; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.MessageSelector; +import tools.refinery.viatra.runtime.rete.network.communication.PhasedSelector; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.indexer.DefaultMessageIndexer; +import tools.refinery.viatra.runtime.rete.network.mailbox.AdaptableMailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; + +/** + * A mailbox implementation that splits updates messages according to the standard subset ordering into anti-monotonic + * (deletions) and monotonic (insertions) updates. + * + * @author Tamas Szabo + * @since 2.0 + */ +public class UpdateSplittingMailbox extends AbstractUpdateSplittingMailbox + implements AdaptableMailbox { + + protected Mailbox adapter; + + public UpdateSplittingMailbox(final Receiver receiver, final ReteContainer container) { + super(receiver, container, DefaultMessageIndexer::new); + this.adapter = this; + } + + @Override + public Mailbox getAdapter() { + return this.adapter; + } + + @Override + public void setAdapter(final Mailbox adapter) { + this.adapter = adapter; + } + + @Override + public void postMessage(final Direction direction, final Tuple update, final Timestamp timestamp) { + final DefaultMessageIndexer monotoneQueue = getActiveMonotoneQueue(); + final DefaultMessageIndexer antiMonotoneQueue = getActiveAntiMonotoneQueue(); + final boolean wasPresentAsMonotone = monotoneQueue.getCount(update) != 0; + final boolean wasPresentAsAntiMonotone = antiMonotoneQueue.getCount(update) != 0; + + // it cannot happen that it was present in both + assert !(wasPresentAsMonotone && wasPresentAsAntiMonotone); + + if (direction == Direction.INSERT) { + if (wasPresentAsAntiMonotone) { + // it was an anti-monotone one before + antiMonotoneQueue.insert(update); + } else { + // it was a monotone one before or did not exist at all + monotoneQueue.insert(update); + } + } else { + if (wasPresentAsMonotone) { + // it was a monotone one before + monotoneQueue.delete(update); + } else { + // it was an anti-monotone one before or did not exist at all + antiMonotoneQueue.delete(update); + } + } + + final Mailbox targetMailbox = this.adapter; + final CommunicationGroup targetGroup = this.adapter.getCurrentGroup(); + + if (antiMonotoneQueue.isEmpty()) { + targetGroup.notifyLostAllMessages(targetMailbox, PhasedSelector.ANTI_MONOTONE); + } else { + targetGroup.notifyHasMessage(targetMailbox, PhasedSelector.ANTI_MONOTONE); + } + + if (monotoneQueue.isEmpty()) { + targetGroup.notifyLostAllMessages(targetMailbox, PhasedSelector.MONOTONE); + } else { + targetGroup.notifyHasMessage(targetMailbox, PhasedSelector.MONOTONE); + } + } + + @Override + public void deliverAll(final MessageSelector kind) { + if (kind == PhasedSelector.ANTI_MONOTONE) { + // deliver anti-monotone + this.deliveringAntiMonotone = true; + for (final Entry entry : this.antiMonotoneQueue.getTuples().entrySet()) { + final Tuple update = entry.getKey(); + final int count = entry.getValue(); + assert count < 0; + for (int i = 0; i < Math.abs(count); i++) { + this.receiver.update(Direction.DELETE, update, Timestamp.ZERO); + } + } + this.deliveringAntiMonotone = false; + swapAndClearAntiMonotone(); + } else if (kind == PhasedSelector.MONOTONE) { + // deliver monotone + this.deliveringMonotone = true; + for (final Entry entry : this.monotoneQueue.getTuples().entrySet()) { + final Tuple update = entry.getKey(); + final int count = entry.getValue(); + assert count > 0; + for (int i = 0; i < count; i++) { + this.receiver.update(Direction.INSERT, update, Timestamp.ZERO); + } + } + this.deliveringMonotone = false; + swapAndClearMonotone(); + } else { + throw new IllegalArgumentException("Unsupported message kind " + kind); + } + } + + @Override + public String toString() { + return "US_MBOX (" + this.receiver + ") " + this.getActiveMonotoneQueue() + " " + + this.getActiveAntiMonotoneQueue(); + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timely/TimelyMailbox.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timely/TimelyMailbox.java new file mode 100644 index 00000000..bf3b8e14 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/network/mailbox/timely/TimelyMailbox.java @@ -0,0 +1,150 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.network.mailbox.timely; + +import java.util.Map; +import java.util.TreeMap; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration.TimelineRepresentation; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.MessageSelector; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.communication.timely.ResumableNode; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; + +public class TimelyMailbox implements Mailbox { + + protected TreeMap> queue; + protected final Receiver receiver; + protected final ReteContainer container; + protected CommunicationGroup group; + protected boolean fallThrough; + + public TimelyMailbox(final Receiver receiver, final ReteContainer container) { + this.receiver = receiver; + this.container = container; + this.queue = CollectionsFactory.createTreeMap(); + } + + protected TreeMap> getActiveQueue() { + return this.queue; + } + + @Override + public boolean isEmpty() { + return getActiveQueue().isEmpty(); + } + + @Override + public void postMessage(final Direction direction, final Tuple update, final Timestamp timestamp) { + final TreeMap> activeQueue = getActiveQueue(); + + Map tupleMap = activeQueue.get(timestamp); + final boolean wasEmpty = tupleMap == null; + boolean significantChange = false; + + if (tupleMap == null) { + tupleMap = CollectionsFactory.createMap(); + activeQueue.put(timestamp, tupleMap); + significantChange = true; + } + + Integer count = tupleMap.get(update); + if (count == null) { + count = 0; + significantChange = true; + } + + if (direction == Direction.DELETE) { + count--; + } else { + count++; + } + + if (count == 0) { + tupleMap.remove(update); + if (tupleMap.isEmpty()) { + activeQueue.remove(timestamp); + } + significantChange = true; + } else { + tupleMap.put(update, count); + } + + if (significantChange) { + if (wasEmpty) { + this.group.notifyHasMessage(this, timestamp); + } else if (tupleMap.isEmpty()) { + final Timestamp resumableTimestamp = (this.receiver instanceof ResumableNode) + ? ((ResumableNode) this.receiver).getResumableTimestamp() + : null; + // check if there is folding left to do before unsubscribing just based on the message queue being empty + if (resumableTimestamp == null || resumableTimestamp.compareTo(timestamp) != 0) { + this.group.notifyLostAllMessages(this, timestamp); + } + } + } + } + + @Override + public void deliverAll(final MessageSelector selector) { + if (selector instanceof Timestamp) { + final Timestamp timestamp = (Timestamp) selector; + // REMOVE the tuples associated with the selector, dont just query them + final Map tupleMap = this.queue.remove(timestamp); + + // tupleMap may be empty if we only have lazy folding to do + if (tupleMap != null) { + this.receiver.batchUpdate(tupleMap.entrySet(), timestamp); + } + + if (this.container.getTimelyConfiguration() + .getTimelineRepresentation() == TimelineRepresentation.FAITHFUL) { + // (1) either normal delivery, which ended up being a lazy folding state + // (2) and/or lazy folding needs to be resumed + if (this.receiver instanceof ResumableNode) { + ((ResumableNode) this.receiver).resumeAt(timestamp); + } + } + } else { + throw new IllegalArgumentException("Unsupported message selector " + selector); + } + } + + @Override + public String toString() { + return "DDF_MBOX (" + this.receiver + ") " + this.getActiveQueue(); + } + + @Override + public Receiver getReceiver() { + return this.receiver; + } + + @Override + public void clear() { + this.queue.clear(); + } + + @Override + public CommunicationGroup getCurrentGroup() { + return this.group; + } + + @Override + public void setCurrentGroup(final CommunicationGroup group) { + this.group = group; + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/remote/Address.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/remote/Address.java new file mode 100644 index 00000000..2fed3225 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/remote/Address.java @@ -0,0 +1,125 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.remote; + +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; + +/** + * Remote identifier of a node of type T. + * + * @author Gabor Bergmann + * + */ +public class Address { + ReteContainer container; + Long nodeId; + /** + * Feel free to leave null e.g. if node is in a separate JVM. + */ + T nodeCache; + + /** + * Address of local node (use only for containers in the same VM!) + */ + public static Address of(N node) { + return new Address(node); + } + + /** + * General constructor. + * + * @param container + * @param nodeId + */ + public Address(ReteContainer container, Long nodeId) { + super(); + this.container = container; + this.nodeId = nodeId; + } + + /** + * Local-only constructor. (use only for containers in the same VM!) + * + * @param node + * the node to address + */ + public Address(T node) { + super(); + this.nodeCache = node; + this.container = node.getContainer(); + this.nodeId = node.getNodeId(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((container == null) ? 0 : container.hashCode()); + result = prime * result + ((nodeId == null) ? 0 : nodeId.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof Address)) + return false; + final Address other = (Address) obj; + if (container == null) { + if (other.container != null) + return false; + } else if (!container.equals(other.container)) + return false; + if (nodeId == null) { + if (other.nodeId != null) + return false; + } else if (!nodeId.equals(other.nodeId)) + return false; + return true; + } + + public ReteContainer getContainer() { + return container; + } + + public void setContainer(ReteContainer container) { + this.container = container; + } + + public Long getNodeId() { + return nodeId; + } + + public void setNodeId(Long nodeId) { + this.nodeId = nodeId; + } + + public T getNodeCache() { + return nodeCache; + } + + public void setNodeCache(T nodeCache) { + this.nodeCache = nodeCache; + } + + @Override + public String toString() { + if (nodeCache == null) + return "A(" + nodeId + " @ " + container + ")"; + else + return "A(" + nodeCache + ")"; + + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/remote/RemoteReceiver.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/remote/RemoteReceiver.java new file mode 100644 index 00000000..f7d267af --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/remote/RemoteReceiver.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.remote; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.single.SingleInputNode; + +/** + * This node delivers updates to a remote recipient; no updates are propagated further in this network. + * + * @author Gabor Bergmann + * + */ +public class RemoteReceiver extends SingleInputNode { + + List> targets; + + public RemoteReceiver(ReteContainer reteContainer) { + super(reteContainer); + targets = new ArrayList>(); + } + + public void addTarget(Address target) { + targets.add(target); + } + + @Override + public void pullInto(Collection collector, boolean flush) { + propagatePullInto(collector, flush); + } + + @Override + public void pullIntoWithTimeline(Map> collector, boolean flush) { + throw new UnsupportedOperationException(); + } + + public Collection remotePull(boolean flush) { + return reteContainer.pullContents(this, flush); + } + + public void update(Direction direction, Tuple updateElement, Timestamp timestamp) { + for (Address ad : targets) + reteContainer.sendUpdateToRemoteAddress(ad, direction, updateElement); + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/remote/RemoteSupplier.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/remote/RemoteSupplier.java new file mode 100644 index 00000000..cbe4d177 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/remote/RemoteSupplier.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.remote; + +import java.util.Collection; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.single.SingleInputNode; + +/** + * This node receives updates from a remote supplier; no local updates are expected. + * + * @author Gabor Bergmann + * + */ +public class RemoteSupplier extends SingleInputNode { + + RemoteReceiver counterpart; + + public RemoteSupplier(ReteContainer reteContainer, RemoteReceiver counterpart) { + super(reteContainer); + this.counterpart = counterpart; + counterpart.addTarget(reteContainer.makeAddress(this)); + } + + @Override + public void pullInto(Collection collector, boolean flush) { + Collection pulled = counterpart.remotePull(flush); + collector.addAll(pulled); + } + + @Override + public void pullIntoWithTimeline(Map> collector, boolean flush) { + throw new UnsupportedOperationException(); + } + + @Override + public void update(Direction direction, Tuple updateElement, Timestamp timestamp) { + propagateUpdate(direction, updateElement, timestamp); + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/AbstractUniquenessEnforcerNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/AbstractUniquenessEnforcerNode.java new file mode 100644 index 00000000..e92ce63f --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/AbstractUniquenessEnforcerNode.java @@ -0,0 +1,138 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, itemis AG, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.single; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.index.ProjectionIndexer; +import tools.refinery.viatra.runtime.rete.index.SpecializedProjectionIndexer.ListenerSubscription; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.StandardNode; +import tools.refinery.viatra.runtime.rete.network.Supplier; +import tools.refinery.viatra.runtime.rete.network.Tunnel; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; +import tools.refinery.viatra.runtime.rete.traceability.TraceInfo; +import tools.refinery.viatra.runtime.rete.util.Options; + +/** + * Ensures that no identical copies get to the output. Only one replica of each pattern substitution may traverse this + * node. There are both timeless and timely implementations. + * + * @author Gabor Bergmann + * @author Tamas Szabo + * @noinstantiate This class is not intended to be instantiated by clients. + * @noextend This class is not intended to be subclassed by clients. + * @since 2.2 + */ +public abstract class AbstractUniquenessEnforcerNode extends StandardNode implements Tunnel { + + protected final Collection parents; + protected ProjectionIndexer memoryNullIndexer; + protected ProjectionIndexer memoryIdentityIndexer; + protected final int tupleWidth; + // MUST BE INSTANTIATED IN THE CONCRETE SUBCLASSES AFTER ALL FIELDS ARE SET + protected Mailbox mailbox; + protected final TupleMask nullMask; + protected final TupleMask identityMask; + protected final List specializedListeners; + + public AbstractUniquenessEnforcerNode(final ReteContainer reteContainer, final int tupleWidth) { + super(reteContainer); + this.parents = new ArrayList(); + this.specializedListeners = new ArrayList(); + this.tupleWidth = tupleWidth; + this.nullMask = TupleMask.linear(0, tupleWidth); + this.identityMask = TupleMask.identity(tupleWidth); + } + + protected abstract Mailbox instantiateMailbox(); + + @Override + public Mailbox getMailbox() { + return this.mailbox; + } + + /** + * @since 2.8 + */ + public abstract Set getTuples(); + + /** + * @since 2.4 + */ + protected void propagate(final Direction direction, final Tuple update, final Timestamp timestamp) { + // See Bug 518434 + // trivial (non-active) indexers must be updated before other listeners + // so that if they are joined against each other, trivial indexers lookups + // will be consistent with their notifications; + // also, their subscriptions must share a single order + for (final ListenerSubscription subscription : specializedListeners) { + subscription.propagate(direction, update, timestamp); + } + propagateUpdate(direction, update, timestamp); + } + + @Override + public ProjectionIndexer constructIndex(final TupleMask mask, final TraceInfo... traces) { + if (Options.employTrivialIndexers) { + if (nullMask.equals(mask)) { + final ProjectionIndexer indexer = getNullIndexer(); + for (final TraceInfo traceInfo : traces) { + indexer.assignTraceInfo(traceInfo); + } + return indexer; + } + if (identityMask.equals(mask)) { + final ProjectionIndexer indexer = getIdentityIndexer(); + for (final TraceInfo traceInfo : traces) { + indexer.assignTraceInfo(traceInfo); + } + return indexer; + } + } + return super.constructIndex(mask, traces); + } + + public abstract ProjectionIndexer getNullIndexer(); + + public abstract ProjectionIndexer getIdentityIndexer(); + + @Override + public void appendParent(final Supplier supplier) { + parents.add(supplier); + } + + @Override + public void removeParent(final Supplier supplier) { + parents.remove(supplier); + } + + @Override + public Collection getParents() { + return parents; + } + + @Override + public void assignTraceInfo(final TraceInfo traceInfo) { + super.assignTraceInfo(traceInfo); + if (traceInfo.propagateFromStandardNodeToSupplierParent()) { + for (final Supplier parent : parents) { + parent.acceptPropagatedTraceInfo(traceInfo); + } + } + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/CallbackNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/CallbackNode.java new file mode 100644 index 00000000..c68036b5 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/CallbackNode.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.single; + +import tools.refinery.viatra.runtime.matchers.backend.IUpdateable; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.rete.misc.SimpleReceiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * @author Bergmann Gabor + * + */ +public class CallbackNode extends SimpleReceiver { + + IUpdateable updateable; + + public CallbackNode(ReteContainer reteContainer, IUpdateable updateable) + { + super(reteContainer); + this.updateable = updateable; + } + + @Override + public void update(Direction direction, Tuple updateElement, Timestamp timestamp) { + updateable.update(updateElement, direction == Direction.INSERT); + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/DefaultProductionNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/DefaultProductionNode.java new file mode 100644 index 00000000..eca8bc17 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/DefaultProductionNode.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.single; + +import java.util.Iterator; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.context.IPosetComparator; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.rete.network.ProductionNode; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.traceability.CompiledQuery; +import tools.refinery.viatra.runtime.rete.traceability.TraceInfo; + +/** + * Default implementation of the Production node, based on UniquenessEnforcerNode + * + * @author Gabor Bergmann + * @noinstantiate This class is not intended to be instantiated by clients. + */ +public class DefaultProductionNode extends UniquenessEnforcerNode implements ProductionNode { + + protected final Map posMapping; + + /** + * @since 1.6 + */ + public DefaultProductionNode(final ReteContainer reteContainer, final Map posMapping, + final boolean deleteRederiveEvaluation) { + this(reteContainer, posMapping, deleteRederiveEvaluation, null, null, null); + } + + /** + * @since 1.6 + */ + public DefaultProductionNode(final ReteContainer reteContainer, final Map posMapping, + final boolean deleteRederiveEvaluation, final TupleMask coreMask, final TupleMask posetMask, + final IPosetComparator posetComparator) { + super(reteContainer, posMapping.size(), deleteRederiveEvaluation, coreMask, posetMask, posetComparator); + this.posMapping = posMapping; + } + + @Override + public Map getPosMapping() { + return posMapping; + } + + @Override + public Iterator iterator() { + return memory.iterator(); + } + + @Override + public void acceptPropagatedTraceInfo(final TraceInfo traceInfo) { + if (traceInfo.propagateToProductionNodeParentAlso()) { + super.acceptPropagatedTraceInfo(traceInfo); + } + } + + @Override + public String toString() { + for (final TraceInfo traceInfo : this.traceInfos) { + if (traceInfo instanceof CompiledQuery) { + final String patternName = ((CompiledQuery) traceInfo).getPatternName(); + return String.format(this.getClass().getName() + "<%s>=%s", patternName, super.toString()); + } + } + return super.toString(); + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/DiscriminatorBucketNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/DiscriminatorBucketNode.java new file mode 100644 index 00000000..803bab20 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/DiscriminatorBucketNode.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.single; + +import java.util.Collection; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.Supplier; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * A bucket holds a filtered set of tuples of its parent {@link DiscriminatorDispatcherNode}. + * Exactly those that have the given bucket key at their discrimination column. + * + *

    During operation, tuple contents and bucket keys have already been wrapped using {@link IQueryRuntimeContext#wrapElement(Object)} + * + * @author Gabor Bergmann + * @since 1.5 + */ +public class DiscriminatorBucketNode extends SingleInputNode { + + private Object bucketKey; + + /** + * @param bucketKey will be wrapped using {@link IQueryRuntimeContext#wrapElement(Object)} + + */ + public DiscriminatorBucketNode(ReteContainer reteContainer, Object bucketKey) { + super(reteContainer); + this.bucketKey = reteContainer.getNetwork().getEngine().getRuntimeContext().wrapElement(bucketKey); + } + + @Override + public void pullInto(final Collection collector, final boolean flush) { + if (parent != null) { + getDispatcher().pullIntoFiltered(collector, bucketKey, flush); + } + } + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + if (parent != null) { + getDispatcher().pullIntoWithTimestampFiltered(collector, bucketKey, flush); + } + } + + @Override + public void update(Direction direction, Tuple updateElement, Timestamp timestamp) { + propagateUpdate(direction, updateElement, timestamp); + } + + public Object getBucketKey() { + return bucketKey; + } + + @Override + public void appendParent(Supplier supplier) { + if (! (supplier instanceof DiscriminatorDispatcherNode)) + throw new IllegalArgumentException(); + super.appendParent(supplier); + } + + public DiscriminatorDispatcherNode getDispatcher() { + return (DiscriminatorDispatcherNode) parent; + } + + @Override + protected String toStringCore() { + return String.format("%s<%s=='%s'>", + super.toStringCore(), + (getDispatcher() == null) ? "?" : getDispatcher().getDiscriminationColumnIndex(), + bucketKey); + } +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/DiscriminatorDispatcherNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/DiscriminatorDispatcherNode.java new file mode 100644 index 00000000..a8e11fcd --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/DiscriminatorDispatcherNode.java @@ -0,0 +1,154 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.single; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.NetworkStructureChangeSensitiveNode; +import tools.refinery.viatra.runtime.rete.network.Receiver; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; + +/** + * Node that sends tuples off to different buckets (attached as children of type {@link DiscriminatorBucketNode}), based + * on the value of a given column. + * + *

    + * Tuple contents and bucket keys have already been wrapped using {@link IQueryRuntimeContext#wrapElement(Object)} + * + * @author Gabor Bergmann + * @since 1.5 + */ +public class DiscriminatorDispatcherNode extends SingleInputNode implements NetworkStructureChangeSensitiveNode { + + private int discriminationColumnIndex; + private Map buckets = new HashMap<>(); + private Map bucketMailboxes = new HashMap<>(); + + /** + * @param reteContainer + */ + public DiscriminatorDispatcherNode(ReteContainer reteContainer, int discriminationColumnIndex) { + super(reteContainer); + this.discriminationColumnIndex = discriminationColumnIndex; + } + + @Override + public void update(Direction direction, Tuple updateElement, Timestamp timestamp) { + Object dispatchKey = updateElement.get(discriminationColumnIndex); + Mailbox bucketMailBox = bucketMailboxes.get(dispatchKey); + if (bucketMailBox != null) { + bucketMailBox.postMessage(direction, updateElement, timestamp); + } + } + + public int getDiscriminationColumnIndex() { + return discriminationColumnIndex; + } + + @Override + public void pullInto(final Collection collector, final boolean flush) { + propagatePullInto(collector, flush); + } + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + propagatePullIntoWithTimestamp(collector, flush); + } + + /** + * @since 2.3 + */ + public void pullIntoFiltered(final Collection collector, final Object bucketKey, final boolean flush) { + final ArrayList unfiltered = new ArrayList(); + propagatePullInto(unfiltered, flush); + for (Tuple tuple : unfiltered) { + if (bucketKey.equals(tuple.get(discriminationColumnIndex))) { + collector.add(tuple); + } + } + } + + /** + * @since 2.3 + */ + public void pullIntoWithTimestampFiltered(final Map> collector, final Object bucketKey, + final boolean flush) { + final Map> unfiltered = CollectionsFactory.createMap(); + propagatePullIntoWithTimestamp(unfiltered, flush); + for (final Entry> entry : unfiltered.entrySet()) { + if (bucketKey.equals(entry.getKey().get(discriminationColumnIndex))) { + collector.put(entry.getKey(), entry.getValue()); + } + } + } + + @Override + public void appendChild(Receiver receiver) { + super.appendChild(receiver); + if (receiver instanceof DiscriminatorBucketNode) { + DiscriminatorBucketNode bucket = (DiscriminatorBucketNode) receiver; + Object bucketKey = bucket.getBucketKey(); + DiscriminatorBucketNode old = buckets.put(bucketKey, bucket); + if (old != null) { + throw new IllegalStateException(); + } + bucketMailboxes.put(bucketKey, this.getCommunicationTracker().proxifyMailbox(this, bucket.getMailbox())); + } + } + + /** + * @since 2.2 + */ + public Map getBucketMailboxes() { + return this.bucketMailboxes; + } + + @Override + public void networkStructureChanged() { + bucketMailboxes.clear(); + for (Receiver receiver : children) { + if (receiver instanceof DiscriminatorBucketNode) { + DiscriminatorBucketNode bucket = (DiscriminatorBucketNode) receiver; + Object bucketKey = bucket.getBucketKey(); + bucketMailboxes.put(bucketKey, + this.getCommunicationTracker().proxifyMailbox(this, bucket.getMailbox())); + } + } + } + + @Override + public void removeChild(Receiver receiver) { + super.removeChild(receiver); + if (receiver instanceof DiscriminatorBucketNode) { + DiscriminatorBucketNode bucket = (DiscriminatorBucketNode) receiver; + Object bucketKey = bucket.getBucketKey(); + DiscriminatorBucketNode old = buckets.remove(bucketKey); + if (old != bucket) + throw new IllegalStateException(); + bucketMailboxes.remove(bucketKey); + } + } + + @Override + protected String toStringCore() { + return super.toStringCore() + '<' + discriminationColumnIndex + '>'; + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/EqualityFilterNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/EqualityFilterNode.java new file mode 100644 index 00000000..014c2016 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/EqualityFilterNode.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.single; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; + +public class EqualityFilterNode extends FilterNode { + + int[] indices; + int first; + + /** + * @param reteContainer + * @param indices + * indices of the Tuple that should hold equal values + */ + public EqualityFilterNode(ReteContainer reteContainer, int[] indices) { + super(reteContainer); + this.indices = indices; + first = indices[0]; + } + + @Override + public boolean check(Tuple ps) { + Object firstElement = ps.get(first); + for (int i = 1 /* first is omitted */; i < indices.length; i++) { + if (!ps.get(indices[i]).equals(firstElement)) + return false; + } + return true; + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/FilterNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/FilterNode.java new file mode 100644 index 00000000..f66f1715 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/FilterNode.java @@ -0,0 +1,69 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.single; + +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * This node implements a simple filter. A stateless abstract check() predicate determines whether a matching is allowed + * to pass. + * + * @author Gabor Bergmann + * + */ +public abstract class FilterNode extends SingleInputNode { + + public FilterNode(final ReteContainer reteContainer) { + super(reteContainer); + } + + /** + * Abstract filtering predicate. Expected to be stateless. + * + * @param ps + * the matching to be checked. + * @return true if and only if the parameter matching is allowed to pass through this node. + */ + public abstract boolean check(final Tuple ps); + + @Override + public void pullInto(final Collection collector, final boolean flush) { + for (final Tuple ps : this.reteContainer.pullPropagatedContents(this, flush)) { + if (check(ps)) { + collector.add(ps); + } + } + } + + @Override + public void pullIntoWithTimeline(Map> collector, boolean flush) { + for (final Entry> entry : this.reteContainer.pullPropagatedContentsWithTimestamp(this, flush).entrySet()) { + if (check(entry.getKey())) { + collector.put(entry.getKey(), entry.getValue()); + } + } + } + + @Override + public void update(final Direction direction, final Tuple updateElement, final Timestamp timestamp) { + if (check(updateElement)) { + propagateUpdate(direction, updateElement, timestamp); + } + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/InequalityFilterNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/InequalityFilterNode.java new file mode 100644 index 00000000..8dd3e949 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/InequalityFilterNode.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.single; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; + +/** + * This node filters patterns according to equalities and inequalities of elements. The 'subject' element is asserted to + * be different from the elements given by the inequalityMask. + * + * + * @author Gabor Bergmann + * + */ +public class InequalityFilterNode extends FilterNode { + + int subjectIndex; + TupleMask inequalityMask; + + /** + * @param reteContainer + * @param subject + * the index of the element that should be compared. + * @param inequalityMask + * the indices of elements that should be different from the subjectIndex. + */ + public InequalityFilterNode(ReteContainer reteContainer, int subject, TupleMask inequalityMask) { + super(reteContainer); + this.subjectIndex = subject; + this.inequalityMask = inequalityMask; + } + + @Override + public boolean check(Tuple ps) { + Object subject = ps.get(subjectIndex); + for (int ineq : inequalityMask.indices) { + if (subject.equals(ps.get(ineq))) + return false; + } + return true; + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/RepresentativeElectionNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/RepresentativeElectionNode.java new file mode 100644 index 00000000..6a1e305c --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/RepresentativeElectionNode.java @@ -0,0 +1,125 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Gabor Bergmann, Istvan Rath and Daniel Varro + * Copyright (c) 2023 The Refinery Authors + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.single; + +import tools.refinery.viatra.runtime.base.itc.alg.representative.RepresentativeElectionAlgorithm; +import tools.refinery.viatra.runtime.base.itc.alg.representative.RepresentativeObserver; +import tools.refinery.viatra.runtime.base.itc.graphimpl.Graph; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.Clearable; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.ReinitializedNode; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +import java.util.Collection; +import java.util.Map; + +public class RepresentativeElectionNode extends SingleInputNode implements Clearable, RepresentativeObserver, + ReinitializedNode { + private final RepresentativeElectionAlgorithm.Factory algorithmFactory; + private Graph graph; + private RepresentativeElectionAlgorithm algorithm; + + public RepresentativeElectionNode(ReteContainer reteContainer, + RepresentativeElectionAlgorithm.Factory algorithmFactory) { + super(reteContainer); + this.algorithmFactory = algorithmFactory; + graph = new Graph<>(); + algorithm = algorithmFactory.create(graph); + algorithm.setObserver(this); + reteContainer.registerClearable(this); + } + + @Override + public void networkStructureChanged() { + if (reteContainer.isTimelyEvaluation() && reteContainer.getCommunicationTracker().isInRecursiveGroup(this)) { + throw new IllegalStateException(this + " cannot be used in recursive differential dataflow evaluation!"); + } + super.networkStructureChanged(); + } + + @Override + public void reinitializeWith(Collection tuples) { + algorithm.dispose(); + graph = new Graph<>(); + for (var tuple : tuples) { + insertEdge(tuple.get(0), tuple.get(1)); + } + algorithm = algorithmFactory.create(graph); + algorithm.setObserver(this); + } + + @Override + public void tupleChanged(Object source, Object representative, Direction direction) { + var tuple = Tuples.staticArityFlatTupleOf(source, representative); + propagateUpdate(direction, tuple, Timestamp.ZERO); + } + + @Override + public void clear() { + algorithm.dispose(); + graph = new Graph<>(); + algorithm = algorithmFactory.create(graph); + } + + @Override + public void update(Direction direction, Tuple updateElement, Timestamp timestamp) { + var source = updateElement.get(0); + var target = updateElement.get(1); + switch (direction) { + case INSERT -> insertEdge(source, target); + case DELETE -> deleteEdge(source, target); + default -> throw new IllegalArgumentException("Unknown direction: " + direction); + } + } + + private void insertEdge(Object source, Object target) { + graph.insertNode(source); + graph.insertNode(target); + graph.insertEdge(source, target); + } + + private void deleteEdge(Object source, Object target) { + graph.deleteEdgeIfExists(source, target); + if (isIsolated(source)) { + graph.deleteNode(source); + } + if (!source.equals(target) && isIsolated(target)) { + graph.deleteNode(target); + } + } + + private boolean isIsolated(Object node) { + return graph.getTargetNodes(node).isEmpty() && graph.getSourceNodes(node).isEmpty(); + } + + @Override + public void pullInto(Collection collector, boolean flush) { + for (var entry : algorithm.getComponents().entrySet()) { + var representative = entry.getKey(); + for (var node : entry.getValue()) { + collector.add(Tuples.staticArityFlatTupleOf(node, representative)); + } + } + } + + @Override + public void pullIntoWithTimeline(Map> collector, boolean flush) { + // Use all zero timestamps because this node cannot be used in recursive groups anyway. + for (var entry : algorithm.getComponents().entrySet()) { + var representative = entry.getKey(); + for (var node : entry.getValue()) { + collector.put(Tuples.staticArityFlatTupleOf(node, representative), Timestamp.INSERT_AT_ZERO_TIMELINE); + } + } + } +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/SingleInputNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/SingleInputNode.java new file mode 100644 index 00000000..99fc45b2 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/SingleInputNode.java @@ -0,0 +1,126 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.single; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.StandardNode; +import tools.refinery.viatra.runtime.rete.network.Supplier; +import tools.refinery.viatra.runtime.rete.network.Tunnel; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationTracker; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.timeless.BehaviorChangingMailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.timely.TimelyMailbox; +import tools.refinery.viatra.runtime.rete.traceability.TraceInfo; + +/** + * @author Gabor Bergmann + * + */ +public abstract class SingleInputNode extends StandardNode implements Tunnel { + + protected Supplier parent; + /** + * @since 1.6 + */ + protected Mailbox mailbox; + + public SingleInputNode(ReteContainer reteContainer) { + super(reteContainer); + mailbox = instantiateMailbox(); + reteContainer.registerClearable(mailbox); + parent = null; + } + + /** + * Instantiates the {@link Mailbox} of this receiver. + * Subclasses may override this method to provide their own mailbox implementation. + * + * @return the mailbox + * @since 2.0 + */ + protected Mailbox instantiateMailbox() { + if (this.reteContainer.isTimelyEvaluation()) { + return new TimelyMailbox(this, this.reteContainer); + } else { + return new BehaviorChangingMailbox(this, this.reteContainer); + } + } + + @Override + public CommunicationTracker getCommunicationTracker() { + return this.reteContainer.getCommunicationTracker(); + } + + @Override + public Mailbox getMailbox() { + return this.mailbox; + } + + @Override + public void appendParent(Supplier supplier) { + if (parent == null) + parent = supplier; + else + throw new UnsupportedOperationException("Illegal RETE edge: " + this + " already has a parent (" + parent + + ") and cannot connect to additional parent (" + supplier + + ") as it is not a Uniqueness Enforcer Node. "); + } + + @Override + public void removeParent(Supplier supplier) { + if (parent == supplier) + parent = null; + else + throw new IllegalArgumentException("Illegal RETE edge removal: the parent of " + this + " is not " + + supplier); + } + + /** + * To be called by derived classes and ReteContainer. + */ + public void propagatePullInto(final Collection collector, final boolean flush) { + if (parent != null) { + parent.pullInto(collector, flush); + } + } + + /** + * To be called by derived classes and ReteContainer. + */ + public void propagatePullIntoWithTimestamp(final Map> collector, final boolean flush) { + if (parent != null) { + parent.pullIntoWithTimeline(collector, flush); + } + } + + @Override + public Collection getParents() { + if (parent == null) + return Collections.emptySet(); + else + return Collections.singleton(parent); + } + + @Override + public void assignTraceInfo(TraceInfo traceInfo) { + super.assignTraceInfo(traceInfo); + if (traceInfo.propagateFromStandardNodeToSupplierParent()) + if (parent != null) + parent.acceptPropagatedTraceInfo(traceInfo); + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TimelyProductionNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TimelyProductionNode.java new file mode 100644 index 00000000..82640948 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TimelyProductionNode.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.single; + +import java.util.Iterator; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.rete.network.ProductionNode; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.traceability.CompiledQuery; +import tools.refinery.viatra.runtime.rete.traceability.TraceInfo; +/** + * Differential dataflow implementation of the Production node, based on {@link TimelyUniquenessEnforcerNode}. + * + * @author Tamas Szabo + * @noinstantiate This class is not intended to be instantiated by clients. + * @since 2.3 + */ +public class TimelyProductionNode extends TimelyUniquenessEnforcerNode implements ProductionNode { + + protected final Map posMapping; + + public TimelyProductionNode(final ReteContainer reteContainer, final Map posMapping) { + super(reteContainer, posMapping.size()); + this.posMapping = posMapping; + } + + @Override + public Map getPosMapping() { + return this.posMapping; + } + + @Override + public Iterator iterator() { + return this.memory.keySet().iterator(); + } + + @Override + public void acceptPropagatedTraceInfo(final TraceInfo traceInfo) { + if (traceInfo.propagateToProductionNodeParentAlso()) { + super.acceptPropagatedTraceInfo(traceInfo); + } + } + + @Override + public String toString() { + for (final TraceInfo traceInfo : this.traceInfos) { + if (traceInfo instanceof CompiledQuery) { + final String patternName = ((CompiledQuery) traceInfo).getPatternName(); + return String.format(this.getClass().getName() + "<%s>=%s", patternName, super.toString()); + } + } + return super.toString(); + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TimelyUniquenessEnforcerNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TimelyUniquenessEnforcerNode.java new file mode 100644 index 00000000..4c4b4fc0 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TimelyUniquenessEnforcerNode.java @@ -0,0 +1,161 @@ +/******************************************************************************* + * Copyright (c) 2010-2019, Tamas Szabo, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.single; + +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.Signed; +import tools.refinery.viatra.runtime.matchers.util.TimelyMemory; +import tools.refinery.viatra.runtime.matchers.util.timeline.Diff; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.index.ProjectionIndexer; +import tools.refinery.viatra.runtime.rete.index.timely.TimelyMemoryIdentityIndexer; +import tools.refinery.viatra.runtime.rete.index.timely.TimelyMemoryNullIndexer; +import tools.refinery.viatra.runtime.rete.matcher.TimelyConfiguration.TimelineRepresentation; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.communication.timely.ResumableNode; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.timely.TimelyMailbox; + +/** + * Timely uniqueness enforcer node implementation. + * + * @author Tamas Szabo + * @noinstantiate This class is not intended to be instantiated by clients. + * @noextend This class is not intended to be subclassed by clients. + * @since 2.4 + */ +public class TimelyUniquenessEnforcerNode extends AbstractUniquenessEnforcerNode implements ResumableNode { + + protected final TimelyMemory memory; + /** + * @since 2.4 + */ + protected CommunicationGroup group; + + public TimelyUniquenessEnforcerNode(final ReteContainer container, final int tupleWidth) { + super(container, tupleWidth); + this.memory = new TimelyMemory( + container.getTimelyConfiguration().getTimelineRepresentation() == TimelineRepresentation.FAITHFUL); + container.registerClearable(this.memory); + this.mailbox = instantiateMailbox(); + container.registerClearable(this.mailbox); + } + + protected Mailbox instantiateMailbox() { + return new TimelyMailbox(this, this.reteContainer); + } + + @Override + public void pullInto(final Collection collector, final boolean flush) { + for (final Tuple tuple : this.memory.getTuplesAtInfinity()) { + collector.add(tuple); + } + } + + @Override + public CommunicationGroup getCurrentGroup() { + return this.group; + } + + @Override + public void setCurrentGroup(final CommunicationGroup group) { + this.group = group; + } + + @Override + public Set getTuples() { + return this.memory.getTuplesAtInfinity(); + } + + /** + * @since 2.4 + */ + @Override + public Timestamp getResumableTimestamp() { + return this.memory.getResumableTimestamp(); + } + + /** + * @since 2.4 + */ + @Override + public void resumeAt(final Timestamp timestamp) { + final Map> diffMap = this.memory.resumeAt(timestamp); + for (final Entry> entry : diffMap.entrySet()) { + for (final Signed signed : entry.getValue()) { + propagate(signed.getDirection(), entry.getKey(), signed.getPayload()); + } + } + final Timestamp nextTimestamp = this.memory.getResumableTimestamp(); + if (nextTimestamp != null) { + this.group.notifyHasMessage(this.mailbox, nextTimestamp); + } + } + + @Override + public void update(final Direction direction, final Tuple update, final Timestamp timestamp) { + Diff resultDiff = null; + if (direction == Direction.INSERT) { + resultDiff = this.memory.put(update, timestamp); + } else { + try { + resultDiff = this.memory.remove(update, timestamp); + } catch (final IllegalStateException e) { + issueError("[INTERNAL ERROR] Duplicate deletion of " + update + " was detected in " + + this.getClass().getName() + " " + this + " for pattern(s) " + + getTraceInfoPatternsEnumerated(), e); + // diff will remain unset in case of the exception, it is time to return + return; + } + } + + for (final Signed signed : resultDiff) { + propagate(signed.getDirection(), update, signed.getPayload()); + } + } + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + collector.putAll(this.memory.asMap()); + } + + @Override + public ProjectionIndexer getNullIndexer() { + if (this.memoryNullIndexer == null) { + this.memoryNullIndexer = new TimelyMemoryNullIndexer(this.reteContainer, this.tupleWidth, this.memory, this, + this, this.specializedListeners); + this.getCommunicationTracker().registerDependency(this, this.memoryNullIndexer); + } + return this.memoryNullIndexer; + } + + @Override + public ProjectionIndexer getIdentityIndexer() { + if (this.memoryIdentityIndexer == null) { + this.memoryIdentityIndexer = new TimelyMemoryIdentityIndexer(this.reteContainer, this.tupleWidth, + this.memory, this, this, this.specializedListeners); + this.getCommunicationTracker().registerDependency(this, this.memoryIdentityIndexer); + } + return this.memoryIdentityIndexer; + } + + @Override + public void networkStructureChanged() { + super.networkStructureChanged(); + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TransformerNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TransformerNode.java new file mode 100644 index 00000000..24750656 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TransformerNode.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.single; + +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +public abstract class TransformerNode extends SingleInputNode { + + public TransformerNode(final ReteContainer reteContainer) { + super(reteContainer); + } + + protected abstract Tuple transform(final Tuple input); + + @Override + public void pullInto(final Collection collector, final boolean flush) { + for (Tuple ps : reteContainer.pullPropagatedContents(this, flush)) { + collector.add(transform(ps)); + } + } + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + for (final Entry> entry : reteContainer.pullPropagatedContentsWithTimestamp(this, flush).entrySet()) { + collector.put(transform(entry.getKey()), entry.getValue()); + } + } + + @Override + public void update(final Direction direction, final Tuple updateElement, final Timestamp timestamp) { + propagateUpdate(direction, transform(updateElement), timestamp); + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TransitiveClosureNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TransitiveClosureNode.java new file mode 100644 index 00000000..eeead31b --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TransitiveClosureNode.java @@ -0,0 +1,147 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Tamas Szabo, Gabor Bergmann, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.single; + +import tools.refinery.viatra.runtime.base.itc.alg.incscc.IncSCCAlg; +import tools.refinery.viatra.runtime.base.itc.alg.misc.Tuple; +import tools.refinery.viatra.runtime.base.itc.graphimpl.Graph; +import tools.refinery.viatra.runtime.base.itc.igraph.ITcDataSource; +import tools.refinery.viatra.runtime.base.itc.igraph.ITcObserver; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.Clearable; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.NetworkStructureChangeSensitiveNode; +import tools.refinery.viatra.runtime.rete.network.ReinitializedNode; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +import java.util.Collection; +import java.util.Map; + +/** + * This class represents a transitive closure node in the Rete net. + *

    + * This node must not be used in recursive {@link CommunicationGroup}s. + * + * @author Gabor Bergmann + * + */ +public class TransitiveClosureNode extends SingleInputNode + implements Clearable, ITcObserver, NetworkStructureChangeSensitiveNode, ReinitializedNode { + + private Graph graphDataSource; + private ITcDataSource transitiveClosureAlgorithm; + + /** + * Create a new transitive closure rete node. + * + * Client may optionally call {@link #reinitializeWith(Collection)} before using the node, instead of inserting the + * initial set of tuples one by one. + * + * @param reteContainer + * the rete container of the node + */ + public TransitiveClosureNode(ReteContainer reteContainer) { + super(reteContainer); + graphDataSource = new Graph(); + transitiveClosureAlgorithm = new IncSCCAlg(graphDataSource); + transitiveClosureAlgorithm.attachObserver(this); + reteContainer.registerClearable(this); + } + + @Override + public void networkStructureChanged() { + if (this.reteContainer.isTimelyEvaluation() && this.reteContainer.getCommunicationTracker().isInRecursiveGroup(this)) { + throw new IllegalStateException(this.toString() + " cannot be used in recursive differential dataflow evaluation!"); + } + super.networkStructureChanged(); + } + + /** + * Initializes the graph data source with the given collection of tuples. + * + * @param tuples + * the initial collection of tuples + */ + @Override + public void reinitializeWith(Collection tuples) { + clear(); + + for (tools.refinery.viatra.runtime.matchers.tuple.Tuple t : tuples) { + graphDataSource.insertNode(t.get(0)); + graphDataSource.insertNode(t.get(1)); + graphDataSource.insertEdge(t.get(0), t.get(1)); + } + transitiveClosureAlgorithm.attachObserver(this); + } + + @Override + public void pullInto(final Collection collector, final boolean flush) { + for (final Tuple tuple : ((IncSCCAlg) transitiveClosureAlgorithm).getTcRelation()) { + collector.add(Tuples.staticArityFlatTupleOf(tuple.getSource(), tuple.getTarget())); + } + } + + @Override + public void pullIntoWithTimeline( + final Map> collector, + final boolean flush) { + // use all zero timestamps because this node cannot be used in recursive groups anyway + for (final Tuple tuple : ((IncSCCAlg) transitiveClosureAlgorithm).getTcRelation()) { + collector.put(Tuples.staticArityFlatTupleOf(tuple.getSource(), tuple.getTarget()), Timestamp.INSERT_AT_ZERO_TIMELINE); + } + } + + @Override + public void update(Direction direction, tools.refinery.viatra.runtime.matchers.tuple.Tuple updateElement, + Timestamp timestamp) { + if (updateElement.getSize() == 2) { + Object source = updateElement.get(0); + Object target = updateElement.get(1); + + if (direction == Direction.INSERT) { + graphDataSource.insertNode(source); + graphDataSource.insertNode(target); + graphDataSource.insertEdge(source, target); + } + if (direction == Direction.DELETE) { + graphDataSource.deleteEdgeIfExists(source, target); + + if (((IncSCCAlg) transitiveClosureAlgorithm).isIsolated(source)) { + graphDataSource.deleteNode(source); + } + if (!source.equals(target) && ((IncSCCAlg) transitiveClosureAlgorithm).isIsolated(target)) { + graphDataSource.deleteNode(target); + } + } + } + } + + @Override + public void clear() { + transitiveClosureAlgorithm.dispose(); + graphDataSource = new Graph(); + transitiveClosureAlgorithm = new IncSCCAlg(graphDataSource); + } + + @Override + public void tupleInserted(Object source, Object target) { + tools.refinery.viatra.runtime.matchers.tuple.Tuple tuple = Tuples.staticArityFlatTupleOf(source, target); + propagateUpdate(Direction.INSERT, tuple, Timestamp.ZERO); + } + + @Override + public void tupleDeleted(Object source, Object target) { + tools.refinery.viatra.runtime.matchers.tuple.Tuple tuple = Tuples.staticArityFlatTupleOf(source, target); + propagateUpdate(Direction.DELETE, tuple, Timestamp.ZERO); + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TransparentNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TransparentNode.java new file mode 100644 index 00000000..6c21a966 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TransparentNode.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.single; + +import java.util.Collection; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; + +/** + * Simply propagates everything. Might be used to join or fork. + * + * @author Gabor Bergmann + */ +public class TransparentNode extends SingleInputNode { + + public TransparentNode(final ReteContainer reteContainer) { + super(reteContainer); + } + + @Override + public void update(final Direction direction, final Tuple updateElement, final Timestamp timestamp) { + propagateUpdate(direction, updateElement, timestamp); + + } + + @Override + public void pullInto(final Collection collector, final boolean flush) { + propagatePullInto(collector, flush); + } + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + propagatePullIntoWithTimestamp(collector, flush); + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TrimmerNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TrimmerNode.java new file mode 100644 index 00000000..8a72138c --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/TrimmerNode.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.single; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; + +/** + * Trims the matchings as specified by a mask. + * + * @author Gabor Bergmann + * + */ +public class TrimmerNode extends TransformerNode { + + protected TupleMask mask; + + /** + * @param reteContainer + * @param mask + * The mask used to trim substitutions. + */ + public TrimmerNode(ReteContainer reteContainer, TupleMask mask) { + super(reteContainer); + this.mask = mask; + } + + public TrimmerNode(ReteContainer reteContainer) { + super(reteContainer); + this.mask = null; + } + + /** + * @return the mask + */ + public TupleMask getMask() { + return mask; + } + + /** + * @param mask + * the mask to set + */ + public void setMask(TupleMask mask) { + this.mask = mask; + } + + @Override + protected Tuple transform(Tuple input) { + return mask.transform(input); + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/UniquenessEnforcerNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/UniquenessEnforcerNode.java new file mode 100644 index 00000000..5bfde248 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/UniquenessEnforcerNode.java @@ -0,0 +1,321 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann, Tamas Szabo and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.single; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import tools.refinery.viatra.runtime.matchers.context.IPosetComparator; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.Direction; +import tools.refinery.viatra.runtime.matchers.util.IMultiset; +import tools.refinery.viatra.runtime.matchers.util.timeline.Timeline; +import tools.refinery.viatra.runtime.rete.index.MemoryIdentityIndexer; +import tools.refinery.viatra.runtime.rete.index.MemoryNullIndexer; +import tools.refinery.viatra.runtime.rete.index.ProjectionIndexer; +import tools.refinery.viatra.runtime.rete.network.PosetAwareReceiver; +import tools.refinery.viatra.runtime.rete.network.RederivableNode; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; +import tools.refinery.viatra.runtime.rete.network.communication.CommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.communication.Timestamp; +import tools.refinery.viatra.runtime.rete.network.communication.timeless.RecursiveCommunicationGroup; +import tools.refinery.viatra.runtime.rete.network.mailbox.Mailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.timeless.BehaviorChangingMailbox; +import tools.refinery.viatra.runtime.rete.network.mailbox.timeless.PosetAwareMailbox; + +/** + * Timeless uniqueness enforcer node implementation. + *

    + * The node is capable of operating in the delete and re-derive mode. In this mode, it is also possible to equip the + * node with an {@link IPosetComparator} to identify monotone changes; thus, ensuring that a fix-point can be reached + * during the evaluation. + * + * @author Gabor Bergmann + * @author Tamas Szabo + * @noinstantiate This class is not intended to be instantiated by clients. + * @noextend This class is not intended to be subclassed by clients. + */ +public class UniquenessEnforcerNode extends AbstractUniquenessEnforcerNode + implements RederivableNode, PosetAwareReceiver { + + protected IMultiset memory; + /** + * @since 1.6 + */ + protected IMultiset rederivableMemory; + /** + * @since 1.6 + */ + protected boolean deleteRederiveEvaluation; + + /** + * @since 1.7 + */ + protected CommunicationGroup currentGroup; + + public UniquenessEnforcerNode(final ReteContainer reteContainer, final int tupleWidth) { + this(reteContainer, tupleWidth, false); + } + + /** + * OPTIONAL ELEMENT - ONLY PRESENT IF MONOTONICITY INFO WAS AVAILABLE + * + * @since 1.6 + */ + protected final TupleMask coreMask; + /** + * OPTIONAL ELEMENTS - ONLY PRESENT IF MONOTONICITY INFO WAS AVAILABLE + * + * @since 1.6 + */ + protected final TupleMask posetMask; + /** + * OPTIONAL ELEMENTS - ONLY PRESENT IF MONOTONICITY INFO WAS AVAILABLE + * + * @since 1.6 + */ + protected final IPosetComparator posetComparator; + + /** + * @since 1.6 + */ + public UniquenessEnforcerNode(final ReteContainer reteContainer, final int tupleWidth, + final boolean deleteRederiveEvaluation) { + this(reteContainer, tupleWidth, deleteRederiveEvaluation, null, null, null); + } + + /** + * @since 1.6 + */ + public UniquenessEnforcerNode(final ReteContainer reteContainer, final int tupleWidth, + final boolean deleteRederiveEvaluation, final TupleMask coreMask, final TupleMask posetMask, + final IPosetComparator posetComparator) { + super(reteContainer, tupleWidth); + this.memory = CollectionsFactory.createMultiset(); + this.rederivableMemory = CollectionsFactory.createMultiset(); + reteContainer.registerClearable(this.memory); + reteContainer.registerClearable(this.rederivableMemory); + this.deleteRederiveEvaluation = deleteRederiveEvaluation; + this.coreMask = coreMask; + this.posetMask = posetMask; + this.posetComparator = posetComparator; + this.mailbox = instantiateMailbox(); + reteContainer.registerClearable(this.mailbox); + } + + @Override + public void pullInto(final Collection collector, final boolean flush) { + for (final Tuple tuple : this.memory.distinctValues()) { + collector.add(tuple); + } + } + + /** + * @since 2.8 + */ + @Override + public Set getTuples() { + return this.memory.distinctValues(); + } + + @Override + public boolean isInDRedMode() { + return this.deleteRederiveEvaluation; + } + + @Override + public TupleMask getCoreMask() { + return coreMask; + } + + @Override + public TupleMask getPosetMask() { + return posetMask; + } + + @Override + public IPosetComparator getPosetComparator() { + return posetComparator; + } + + @Override + public void pullIntoWithTimeline(final Map> collector, final boolean flush) { + throw new UnsupportedOperationException("Use the timely version of this node!"); + } + + /** + * @since 2.0 + */ + protected Mailbox instantiateMailbox() { + if (coreMask != null && posetMask != null && posetComparator != null) { + return new PosetAwareMailbox(this, this.reteContainer); + } else { + return new BehaviorChangingMailbox(this, this.reteContainer); + } + } + + @Override + public void update(final Direction direction, final Tuple update, final Timestamp timestamp) { + updateWithPosetInfo(direction, update, false); + } + + @Override + public void updateWithPosetInfo(final Direction direction, final Tuple update, final boolean monotone) { + if (this.deleteRederiveEvaluation) { + if (updateWithDeleteAndRederive(direction, update, monotone)) { + propagate(direction, update, Timestamp.ZERO); + } + } else { + if (updateDefault(direction, update)) { + propagate(direction, update, Timestamp.ZERO); + } + } + } + + /** + * @since 2.4 + */ + protected boolean updateWithDeleteAndRederive(final Direction direction, final Tuple update, + final boolean monotone) { + boolean propagate = false; + + final int memoryCount = memory.getCount(update); + final int rederivableCount = rederivableMemory.getCount(update); + + if (direction == Direction.INSERT) { + // INSERT + if (rederivableCount != 0) { + // the tuple is in the re-derivable memory + rederivableMemory.addOne(update); + if (rederivableMemory.isEmpty()) { + // there is nothing left to be re-derived + // this can happen if the INSERT cancelled out a DELETE + ((RecursiveCommunicationGroup) currentGroup).removeRederivable(this); + } + } else { + // the tuple is in the main memory + propagate = memory.addOne(update); + } + } else { + // DELETE + if (rederivableCount != 0) { + // the tuple is in the re-derivable memory + if (memoryCount != 0) { + issueError("[INTERNAL ERROR] Inconsistent state for " + update + + " because it is present both in the main and re-derivable memory in the UniquenessEnforcerNode " + + this + " for pattern(s) " + getTraceInfoPatternsEnumerated(), null); + } + + try { + rederivableMemory.removeOne(update); + } catch (final IllegalStateException ex) { + issueError( + "[INTERNAL ERROR] Duplicate deletion of " + update + " was detected in UniquenessEnforcer " + + this + " for pattern(s) " + getTraceInfoPatternsEnumerated(), + ex); + } + if (rederivableMemory.isEmpty()) { + // there is nothing left to be re-derived + ((RecursiveCommunicationGroup) currentGroup).removeRederivable(this); + } + } else { + // the tuple is in the main memory + if (monotone) { + propagate = memory.removeOne(update); + } else { + final int count = memoryCount - 1; + if (count > 0) { + if (rederivableMemory.isEmpty()) { + // there is now something to be re-derived + ((RecursiveCommunicationGroup) currentGroup).addRederivable(this); + } + rederivableMemory.addPositive(update, count); + } + memory.clearAllOf(update); + propagate = true; + } + } + } + + return propagate; + } + + /** + * @since 2.4 + */ + protected boolean updateDefault(final Direction direction, final Tuple update) { + boolean propagate = false; + if (direction == Direction.INSERT) { + // INSERT + propagate = memory.addOne(update); + } else { + // DELETE + try { + propagate = memory.removeOne(update); + } catch (final IllegalStateException ex) { + propagate = false; + issueError("[INTERNAL ERROR] Duplicate deletion of " + update + " was detected in " + + this.getClass().getName() + " " + this + " for pattern(s) " + + getTraceInfoPatternsEnumerated(), ex); + } + } + return propagate; + } + + /** + * @since 1.6 + */ + @Override + public void rederiveOne() { + final Tuple update = rederivableMemory.iterator().next(); + final int count = rederivableMemory.getCount(update); + rederivableMemory.clearAllOf(update); + memory.addPositive(update, count); + // if there is no other re-derivable tuple, then unregister the node itself + if (this.rederivableMemory.isEmpty()) { + ((RecursiveCommunicationGroup) currentGroup).removeRederivable(this); + } + propagate(Direction.INSERT, update, Timestamp.ZERO); + } + + @Override + public ProjectionIndexer getNullIndexer() { + if (this.memoryNullIndexer == null) { + this.memoryNullIndexer = new MemoryNullIndexer(this.reteContainer, this.tupleWidth, + this.memory.distinctValues(), this, this, this.specializedListeners); + this.getCommunicationTracker().registerDependency(this, this.memoryNullIndexer); + } + return this.memoryNullIndexer; + } + + @Override + public ProjectionIndexer getIdentityIndexer() { + if (this.memoryIdentityIndexer == null) { + this.memoryIdentityIndexer = new MemoryIdentityIndexer(this.reteContainer, this.tupleWidth, + this.memory.distinctValues(), this, this, this.specializedListeners); + this.getCommunicationTracker().registerDependency(this, this.memoryIdentityIndexer); + } + return this.memoryIdentityIndexer; + } + + @Override + public CommunicationGroup getCurrentGroup() { + return currentGroup; + } + + @Override + public void setCurrentGroup(final CommunicationGroup currentGroup) { + this.currentGroup = currentGroup; + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/ValueBinderFilterNode.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/ValueBinderFilterNode.java new file mode 100644 index 00000000..c641bf6e --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/single/ValueBinderFilterNode.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.single; + +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.rete.network.ReteContainer; + +/** + * A filter node that keeps only those tuples that contain a certain value at a certain position. + * + * @author Bergmann Gabor + * + */ +public class ValueBinderFilterNode extends FilterNode { + + int bindingIndex; + Object bindingValue; + + /** + * @param reteContainer + * @param bindingIndex + * the position in the tuple that should be bound + * @param bindingValue + * the value to which the tuple has to be bound + */ + public ValueBinderFilterNode(ReteContainer reteContainer, int bindingIndex, Object bindingValue) { + super(reteContainer); + this.bindingIndex = bindingIndex; + this.bindingValue = bindingValue; + } + + @Override + public boolean check(Tuple ps) { + return bindingValue.equals(ps.get(bindingIndex)); + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/ActiveNodeConflictTrace.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/ActiveNodeConflictTrace.java new file mode 100644 index 00000000..2055dfe8 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/ActiveNodeConflictTrace.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.traceability; + +import tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe; + +public class ActiveNodeConflictTrace extends RecipeTraceInfo { // TODO implement PatternTraceInfo + RecipeTraceInfo inactiveRecipeTrace; + public ActiveNodeConflictTrace(ReteNodeRecipe recipe, + RecipeTraceInfo parentRecipeTrace, + RecipeTraceInfo inactiveRecipeTrace) { + super(recipe, parentRecipeTrace); + this.inactiveRecipeTrace = inactiveRecipeTrace; + } + public RecipeTraceInfo getInactiveRecipeTrace() { + return inactiveRecipeTrace; + } +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/CompiledQuery.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/CompiledQuery.java new file mode 100644 index 00000000..b8c793c5 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/CompiledQuery.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.traceability; + +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe; + +/** + * Indicates that recipe expresses the finished match set of a query. + * @author Bergmann Gabor + * @noinstantiate This class is not intended to be instantiated by clients. + */ +public class CompiledQuery extends RecipeTraceInfo implements + PatternTraceInfo { + + private PQuery query; + private final Map parentRecipeTracesPerBody; + + /** + * @since 1.6 + */ + public CompiledQuery(ReteNodeRecipe recipe, + Map parentRecipeTraces, + PQuery query) { + super(recipe, parentRecipeTraces.values()); + parentRecipeTracesPerBody = parentRecipeTraces; + this.query = query; + } + public PQuery getQuery() { + return query; + } + + @Override + public String getPatternName() { + return query.getFullyQualifiedName(); + } + + /** + * @since 1.6 + */ + public Map getParentRecipeTracesPerBody() { + return parentRecipeTracesPerBody; + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/CompiledSubPlan.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/CompiledSubPlan.java new file mode 100644 index 00000000..572e943c --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/CompiledSubPlan.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.traceability; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import tools.refinery.viatra.runtime.matchers.planning.SubPlan; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; +import tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe; + +/** + * A trace marker associating a Rete recipe with a query SubPlan. + * + *

    The Rete node represented by the recipe is equivalent to the SubPlan. + *

    Invariant: each variable has at most one index associated with it in the tuple, i.e. no duplicates. + */ +public class CompiledSubPlan extends PlanningTrace { + + public CompiledSubPlan(SubPlan subPlan, List variablesTuple, + ReteNodeRecipe recipe, + Collection parentRecipeTraces) { + super(subPlan, variablesTuple, recipe, parentRecipeTraces); + + // Make sure that each variable occurs only once + Set variablesSet = new HashSet(variablesTuple); + Preconditions.checkState(variablesSet.size() == variablesTuple.size(), + () -> String.format( + "Illegal column duplication (%s) while the query plan %s was compiled into a Rete Recipe %s", + variablesTuple.stream().map(PVariable::getName).collect(Collectors.joining(",")), + subPlan.toShortString(), recipe)); + } + + public CompiledSubPlan(SubPlan subPlan, List variablesTuple, + ReteNodeRecipe recipe, + RecipeTraceInfo... parentRecipeTraces) { + this(subPlan, variablesTuple, recipe, Arrays.asList(parentRecipeTraces)); + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/ParameterProjectionTrace.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/ParameterProjectionTrace.java new file mode 100644 index 00000000..8da1e314 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/ParameterProjectionTrace.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.traceability; + +import java.util.Arrays; +import java.util.Collection; + +import tools.refinery.viatra.runtime.matchers.psystem.PBody; +import tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe; + +/** + * The recipe projects the finished results of a {@link PBody} onto the list of parameters. + * @author Bergmann Gabor + * + */ +public class ParameterProjectionTrace extends RecipeTraceInfo implements PatternTraceInfo { + + public ParameterProjectionTrace(PBody body, ReteNodeRecipe recipe, + RecipeTraceInfo... parentRecipeTraces) { + this(body, recipe, Arrays.asList(parentRecipeTraces)); + } + + public ParameterProjectionTrace(PBody body, ReteNodeRecipe recipe, + Collection parentRecipeTraces) { + super(recipe, parentRecipeTraces); + this.body = body; + } + + PBody body; + + @Override + public String getPatternName() { + return body.getPattern().getFullyQualifiedName(); + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/PatternTraceInfo.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/PatternTraceInfo.java new file mode 100644 index 00000000..fb7ef062 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/PatternTraceInfo.java @@ -0,0 +1,17 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.traceability; + +/** + * One kind of trace marker that merely establishes the pattern for which the node was built. + * @author Bergmann Gabor + */ +public interface PatternTraceInfo extends TraceInfo { + String getPatternName(); +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/PlanningTrace.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/PlanningTrace.java new file mode 100644 index 00000000..c1cc3a69 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/PlanningTrace.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.traceability; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import tools.refinery.viatra.runtime.matchers.planning.SubPlan; +import tools.refinery.viatra.runtime.matchers.psystem.PVariable; +import tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe; + +/** + * A trace marker associating a Rete recipe with a query SubPlan. + * + *

    The recipe may be an auxiliary node; + * see {@link CompiledSubPlan} if it represents the entire SubPlan instead. + */ +public class PlanningTrace extends RecipeTraceInfo implements PatternTraceInfo { + + protected SubPlan subPlan; + protected List variablesTuple; + protected Map posMapping; + + public PlanningTrace(SubPlan subPlan, List variablesTuple, + ReteNodeRecipe recipe, + Collection parentRecipeTraces) { + super(recipe, parentRecipeTraces); + this.subPlan = subPlan; + this.variablesTuple = variablesTuple; + + this.posMapping = new HashMap(); + for (int i = 0; i < variablesTuple.size(); ++i) + posMapping.put(variablesTuple.get(i), i); + } + + public PlanningTrace(SubPlan subPlan, List variablesTuple, + ReteNodeRecipe recipe, + RecipeTraceInfo... parentRecipeTraces) { + this(subPlan, variablesTuple, recipe, Arrays.asList(parentRecipeTraces)); + } + + public SubPlan getSubPlan() { + return subPlan; + } + + @Override + public String getPatternName() { + return subPlan.getBody().getPattern().getFullyQualifiedName(); + } + + public List getVariablesTuple() { + return variablesTuple; + } + + public Map getPosMapping() { + return posMapping; + } + + /** + * Returns a new clone that reinterprets the same compiled form + * as the compiled form of a (potentially different) subPlan. + * Useful e.g. if child plan turns out to be a no-op, or when promoting a {@link PlanningTrace} to {@link CompiledSubPlan}. + */ + public CompiledSubPlan cloneFor(SubPlan newSubPlan) { + return new CompiledSubPlan(newSubPlan, + getVariablesTuple(), + getRecipe(), + getParentRecipeTracesForCloning()); + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/RecipeTraceInfo.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/RecipeTraceInfo.java new file mode 100644 index 00000000..8f610550 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/RecipeTraceInfo.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.traceability; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import tools.refinery.viatra.runtime.rete.network.Node; +import tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe; + +/** + * A trace marker that indicates the recipe for which the node was built. + * @author Bergmann Gabor + */ +public class RecipeTraceInfo implements TraceInfo { + public ReteNodeRecipe getRecipe() {return recipe;} + /** + * For cloning in case of recursion cut-off points, use {@link #getParentRecipeTracesForCloning()} instead. + * @return an unmodifiable view on parent traces, to be constructed before this node (or alongside, in case of recursion) + */ + public List getParentRecipeTraces() {return Collections.unmodifiableList(new ArrayList<>(parentRecipeTraces));} + /** + * Directly return the underlying collection so that changes to it will be transparent. Use only for recursion-tolerant cloning. + * @noreference This method is not intended to be referenced by clients. + */ + public Collection getParentRecipeTracesForCloning() {return parentRecipeTraces;} + @Override + public Node getNode() {return node;} + + private Node node; + ReteNodeRecipe recipe; + ReteNodeRecipe shadowedRecipe; + Collection parentRecipeTraces; + + + public RecipeTraceInfo(ReteNodeRecipe recipe, Collection parentRecipeTraces) { + super(); + this.recipe = recipe; + this.parentRecipeTraces = parentRecipeTraces; //ParentTraceList.from(parentRecipeTraces); + } + public RecipeTraceInfo(ReteNodeRecipe recipe, RecipeTraceInfo... parentRecipeTraces) { + this(recipe, Arrays.asList(parentRecipeTraces)); + } + + @Override + public boolean propagateToIndexerParent() {return false;} + @Override + public boolean propagateFromIndexerToSupplierParent() {return false;} + @Override + public boolean propagateFromStandardNodeToSupplierParent() {return false;} + @Override + public boolean propagateToProductionNodeParentAlso() {return false;} + @Override + public void assignNode(Node node) {this.node = node;} + + /** + * @param knownRecipe a known recipe that is equivalent to the current recipe + */ + public void shadowWithEquivalentRecipe(ReteNodeRecipe knownRecipe) { + this.shadowedRecipe = this.recipe; + this.recipe = knownRecipe; + } + + /** + * Get original recipe shadowed by an equivalent + */ + public ReteNodeRecipe getShadowedRecipe() { + return shadowedRecipe; + } + + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/TraceInfo.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/TraceInfo.java new file mode 100644 index 00000000..e1d440db --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/TraceInfo.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.traceability; + +import tools.refinery.viatra.runtime.rete.network.Node; + + +/** + * Traces the node back to a purpose for which the node was built, + * to explain why the node is there and what it means. + * @author Bergmann Gabor + */ +public interface TraceInfo { + boolean propagateToIndexerParent(); + boolean propagateFromIndexerToSupplierParent(); + boolean propagateFromStandardNodeToSupplierParent(); + boolean propagateToProductionNodeParentAlso(); + + void assignNode(Node node); + Node getNode(); +} +// /** +// * The semantics of the tuples contained in this node. +// * @return a tuple of correct size representing the semantics of each position. +// * @post not null +// */ +// Tuple getSemantics(); \ No newline at end of file diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/UserRequestTrace.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/UserRequestTrace.java new file mode 100644 index 00000000..11e4db32 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/traceability/UserRequestTrace.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.traceability; + +import java.util.Collection; + +import tools.refinery.viatra.runtime.rete.recipes.ReteNodeRecipe; + +// private class AggregatorReferenceIndexTraceInfo extends RecipeTraceInfo { +// RecipeTraceInfo aggregatorNodeRecipeTrace; +// public AggregatorReferenceIndexTraceInfo(ProjectionIndexerRecipe recipe, +// RecipeTraceInfo parentRecipeTrace, +// RecipeTraceInfo aggregatorNodeRecipeTrace) { +// super(recipe, parentRecipeTrace); +// this.aggregatorNodeRecipeTrace = aggregatorNodeRecipeTrace; +// } +// public RecipeTraceInfo getAggregatorNodeRecipeTrace() { +// return aggregatorNodeRecipeTrace; +// } +// } +public class UserRequestTrace extends RecipeTraceInfo { + public UserRequestTrace(ReteNodeRecipe recipe, + Collection parentRecipeTraces) { + super(recipe, parentRecipeTraces); + } + public UserRequestTrace(ReteNodeRecipe recipe, + RecipeTraceInfo... parentRecipeTraces) { + super(recipe, parentRecipeTraces); + } +} \ No newline at end of file diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/util/LexicographicComparator.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/util/LexicographicComparator.java new file mode 100644 index 00000000..0efc50af --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/util/LexicographicComparator.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.util; + +import java.util.Comparator; +import java.util.Iterator; + +/** + * A comparator that compares two iterables based on the lexicographic sorting induced by a comparator on elements. + * @author Bergmann Gabor + * + */ +public class LexicographicComparator implements Comparator> { + + final Comparator elementComparator; + + public LexicographicComparator(Comparator elementComparator) { + super(); + this.elementComparator = elementComparator; + } + + @Override + public int compare(Iterable o1, Iterable o2) { + Iterator it1 = o1.iterator(); + Iterator it2 = o2.iterator(); + + boolean has1, has2, bothHaveNext; + do { + has1 = it1.hasNext(); + has2 = it2.hasNext(); + bothHaveNext = has1 && has2; + if (bothHaveNext) { + T element1 = it1.next(); + T element2 = it2.next(); + int elementComparison = elementComparator.compare(element1, element2); + if (elementComparison != 0) + return elementComparison; + } + } while (bothHaveNext); + if (has1 && !has2) { + return +1; + } else if (!has1 && has2) { + return -1; + } else /*if (!has1 && !has2)*/ { + return 0; + } + } + + + + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/util/Options.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/util/Options.java new file mode 100644 index 00000000..96cc445f --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/util/Options.java @@ -0,0 +1,111 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.util; + +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendHintProvider; +import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; +import tools.refinery.viatra.runtime.matchers.planning.IQueryPlannerStrategy; +import tools.refinery.viatra.runtime.rete.construction.basiclinear.BasicLinearLayout; +import tools.refinery.viatra.runtime.rete.construction.quasitree.QuasiTreeLayout; +import tools.refinery.viatra.runtime.rete.network.communication.timely.TimelyCommunicationGroup; + +/** + * Feature switches. + * @author Gabor Bergmann + * @noreference + */ +public class Options { + + public enum NodeSharingOption { + NEVER, // not recommended, patternmatcher leaks possible + INDEXER_AND_REMOTEPROXY, ALL + } + + public static final NodeSharingOption nodeSharingOption = NodeSharingOption.ALL; + public static final boolean releaseOnetimeIndexers = true; // effective only + // with + // nodesharing + // ==NEVER + + public enum InjectivityStrategy { + EAGER, LAZY + } + + public static final InjectivityStrategy injectivityStrategy = InjectivityStrategy.EAGER; + + public static final boolean enableInheritance = true; + + // public final static boolean useComplementerMask = true; + + public static final boolean employTrivialIndexers = true; + + // public final static boolean synchronous = false; + + public static final int numberOfLocalContainers = 1; + public static final int firstFreeContainer = 0; // 0 if head container is + // free to contain pattern + // bodies, 1 otherwise + + /** + * Enable for internal debugging of Rete communication scheme; + * catches cases where the topological sort is violated by a message sent "backwards" + * @since 1.6 + */ + public static final boolean MONITOR_VIOLATION_OF_RETE_NODEGROUP_TOPOLOGICAL_SORTING = false; + + /** + * Enable for internal debugging of message delivery in {@link TimelyCommunicationGroup}s; + * catches cases when there is a violation of increasing timestamps during message delivery within a group. + * @since 2.3 + */ + public static final boolean MONITOR_VIOLATION_OF_DIFFERENTIAL_DATAFLOW_TIMESTAMPS = false; + + /** + * + * @author Gabor Bergmann + * @noreference + */ + public enum BuilderMethod { + LEGACY, // ONLY with GTASM + PSYSTEM_BASIC_LINEAR, PSYSTEM_QUASITREE; + /** + * @since 1.5 + */ + public IQueryPlannerStrategy layoutStrategy(IQueryBackendContext bContext, IQueryBackendHintProvider hintProvider) { + switch (this) { + case PSYSTEM_BASIC_LINEAR: + return new BasicLinearLayout(bContext); + case PSYSTEM_QUASITREE: + return new QuasiTreeLayout(bContext, hintProvider); + default: + throw new UnsupportedOperationException(); + } + } + } + + public static final BuilderMethod builderMethod = + // BuilderMethod.PSYSTEM_BASIC_LINEAR; + BuilderMethod.PSYSTEM_QUASITREE; + + public enum FunctionalDependencyOption { + OFF, + OPPORTUNISTIC + } + public static final FunctionalDependencyOption functionalDependencyOption = + FunctionalDependencyOption.OPPORTUNISTIC; + + public enum PlanTrimOption { + OFF, + OPPORTUNISTIC + } + public static final PlanTrimOption planTrimOption = + PlanTrimOption.OPPORTUNISTIC; + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/util/OrderingCompareAgent.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/util/OrderingCompareAgent.java new file mode 100644 index 00000000..8b147cf6 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/util/OrderingCompareAgent.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.rete.util; + +import java.util.Comparator; + +/** + * Comparing agent for an ordering. Terminology: the "preferred" item will register as LESS. + * + * @author Gabor Bergmann + * + */ +public abstract class OrderingCompareAgent { + protected T a; + protected T b; + + /** + * @param a + * @param b + */ + public OrderingCompareAgent(T a, T b) { + super(); + this.a = a; + this.b = b; + } + + int result = 0; + + protected abstract void doCompare(); + + /** + * @return the result + */ + public int compare() { + doCompare(); + return result; + } + + // COMPARISON HELPERS + protected boolean isUnknown() { + return result == 0; + } + + /** + * @pre result == 0 + */ + protected boolean consider(int partial) { + if (isUnknown()) + result = partial; + return isUnknown(); + } + + protected boolean swallowBoolean(boolean x) { + return x; + } + + // PREFERENCE FUNCTIONS + protected static int dontCare() { + return 0; + } + + protected static int preferTrue(boolean b1, boolean b2) { + return (b1 ^ b2) ? (b1 ? -1 : +1) : 0; + } + + protected static int preferFalse(boolean b1, boolean b2) { + return (b1 ^ b2) ? (b2 ? -1 : +1) : 0; + } + + protected static int preferLess(Comparable c1, U c2) { + return c1.compareTo(c2); + } + + protected static int preferLess(U c1, U c2, Comparator comp) { + return comp.compare(c1, c2); + } + + protected static int preferMore(Comparable c1, U c2) { + return -c1.compareTo(c2); + } + protected static int preferMore(U c1, U c2, Comparator comp) { + return -comp.compare(c1, c2); + } + +} diff --git a/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/util/ReteHintOptions.java b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/util/ReteHintOptions.java new file mode 100644 index 00000000..6e685253 --- /dev/null +++ b/subprojects/viatra-runtime-rete/src/main/java/tools/refinery/viatra/runtime/rete/util/ReteHintOptions.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.rete.util; + +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.backend.QueryHintOption; +import tools.refinery.viatra.runtime.rete.matcher.DRedReteBackendFactory; + +/** + * Provides key objects (of type {@link QueryHintOption}) for {@link QueryEvaluationHint}s. + * @author Gabor Bergmann + * @since 1.5 + */ +public final class ReteHintOptions { + + private ReteHintOptions() {/*Utility class constructor*/} + + public static final QueryHintOption useDiscriminatorDispatchersForConstantFiltering = + hintOption("useDiscriminatorDispatchersForConstantFiltering", true); + + public static final QueryHintOption prioritizeConstantFiltering = + hintOption("prioritizeConstantFiltering", true); + + public static final QueryHintOption cacheOutputOfEvaluatorsByDefault = + hintOption("cacheOutputOfEvaluatorsByDefault", true); + + /** + * The incremental query evaluator backend can evaluate recursive patterns. + * However, by default, instance models that contain cycles are not supported with recursive queries + * and can lead to incorrect query results. + * Enabling Delete And Rederive (DRED) mode guarantees that recursive query evaluation leads to correct results in these cases as well. + * + *

    As DRED may diminish the performance of incremental maintenance, it is not enabled by default. + * @since 1.6 + * @deprecated Use {@link DRedReteBackendFactory} instead of setting this option to true. + */ + @Deprecated + public static final QueryHintOption deleteRederiveEvaluation = + hintOption("deleteRederiveEvaluation", false); + + /** + * This hint allows the query planner to take advantage of "weakened alternative" suggestions of the meta context. + * For instance, enumerable unary type constraints may be substituted with a simple type filtering where sufficient. + * + * @since 1.6 + */ + public static final QueryHintOption expandWeakenedAlternativeConstraints = + hintOption("expandWeakenedAlternativeConstraints", true); + + // internal helper for conciseness + private static QueryHintOption hintOption(String hintKeyLocalName, T defaultValue) { + return new QueryHintOption<>(ReteHintOptions.class, hintKeyLocalName, defaultValue); + } +} diff --git a/subprojects/viatra-runtime/about.html b/subprojects/viatra-runtime/about.html new file mode 100644 index 00000000..d1d5593a --- /dev/null +++ b/subprojects/viatra-runtime/about.html @@ -0,0 +1,26 @@ + + + + +About + + + +

    About This Content

    + +

    March 18, 2019

    +

    License

    + +

    The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise indicated below, the Content is provided to you under the terms and conditions of the +Eclipse Public License Version 2.0 ("EPL"). A copy of the EPL is available at http://www.eclipse.org/legal/epl-v20.html. +For purposes of the EPL, "Program" will mean the Content.

    + +

    If you did not receive this Content directly from the Eclipse Foundation, the Content is being redistributed by another party ("Redistributor") and different terms and conditions may +apply to your use of any object code in the Content. Check the Redistributor's license that was provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise +indicated below, the terms and conditions of the EPL still apply to any source code in the Content and such source code may be obtained at http://www.eclipse.org.

    + + diff --git a/subprojects/viatra-runtime/build.gradle.kts b/subprojects/viatra-runtime/build.gradle.kts new file mode 100644 index 00000000..5d6e3de6 --- /dev/null +++ b/subprojects/viatra-runtime/build.gradle.kts @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ + +plugins { + id("tools.refinery.gradle.java-library") +} + +dependencies { + api(project(":refinery-viatra-runtime-base")) + api(libs.ecore) + implementation(libs.eclipse) + implementation(libs.emf) + implementation(libs.slf4j.log4j) +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/IExtensions.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/IExtensions.java new file mode 100644 index 00000000..d5e0d51f --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/IExtensions.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime; + +/** + * Interface for storing string constants related to VIATRA Query's extension points. + * + * @author Istvan Rath + * + */ +public interface IExtensions { + + public static final String QUERY_SPECIFICATION_EXTENSION_POINT_ID = ViatraQueryRuntimePlugin.PLUGIN_ID + ".queryspecification"; + + public static final String INJECTOREXTENSIONID = ViatraQueryRuntimePlugin.PLUGIN_ID + ".injectorprovider"; + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/ViatraQueryRuntimePlugin.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/ViatraQueryRuntimePlugin.java new file mode 100644 index 00000000..5fbcdad0 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/ViatraQueryRuntimePlugin.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime; + +import org.eclipse.core.runtime.Plugin; +import tools.refinery.viatra.runtime.internal.ExtensionBasedSurrogateQueryLoader; +import tools.refinery.viatra.runtime.internal.ExtensionBasedSystemDefaultBackendLoader; +import tools.refinery.viatra.runtime.registry.ExtensionBasedQuerySpecificationLoader; +import org.osgi.framework.BundleContext; + +/** + * The activator class controls the plug-in life cycle + */ +public class ViatraQueryRuntimePlugin extends Plugin { + + public static final String PLUGIN_ID = "tools.refinery.viatra.runtime"; + + @Override + public void start(BundleContext context) throws Exception { + super.start(context); + ExtensionBasedSurrogateQueryLoader.instance().loadKnownSurrogateQueriesIntoRegistry(); + ExtensionBasedQuerySpecificationLoader.getInstance().loadRegisteredQuerySpecificationsIntoRegistry(); + ExtensionBasedSystemDefaultBackendLoader.instance().loadKnownBackends(); + } + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/AdvancedViatraQueryEngine.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/AdvancedViatraQueryEngine.java new file mode 100644 index 00000000..21e7dfa3 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/AdvancedViatraQueryEngine.java @@ -0,0 +1,368 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.api; + +import java.lang.reflect.InvocationTargetException; +import java.util.concurrent.Callable; + +import tools.refinery.viatra.runtime.api.scope.QueryScope; +import tools.refinery.viatra.runtime.emf.EMFScope; +import tools.refinery.viatra.runtime.internal.apiimpl.ViatraQueryEngineImpl; +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; +import tools.refinery.viatra.runtime.matchers.backend.IMatcherCapability; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackend; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactory; +import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider; +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; + +/** + * Advanced interface to a VIATRA incremental evaluation engine. + * + *

    + * You can create a new, private, unmanaged {@link AdvancedViatraQueryEngine} instance using + * {@link #createUnmanagedEngine(QueryScope)}. Additionally, you can access the advanced interface on any + * {@link ViatraQueryEngine} by {@link AdvancedViatraQueryEngine#from(ViatraQueryEngine)}. + * + *

    + * While the default interface {@link ViatraQueryEngine}, is suitable for most users, this advanced interface provides more + * control over the engine. The most important added functionality is the following: + *

      + *
    • You can have tighter control over the lifecycle of the engine, if you create a private, unmanaged engine + * instance. For instance, a (non-managed) engine can be disposed in order to detach from the EMF model and stop + * listening on update notifications. The indexes built previously in the engine can then be garbage collected, even if + * the model itself is retained. Total lifecycle control is only available for private, unmanaged engines (created using + * {@link #createUnmanagedEngine(QueryScope)}); a managed engine (obtained via {@link ViatraQueryEngine#on(QueryScope)}) is + * shared among clients and can not be disposed or wiped. + *
    • You can add and remove listeners to receive notification when the model or the match sets change. + *
    • You can add and remove listeners to receive notification on engine lifecycle events, such as creation of new + * matchers. For instance, if you explicitly share a private, unmanaged engine between multiple sites, you should + * register a callback using {@link #addLifecycleListener(ViatraQueryEngineLifecycleListener)} to learn when another client + * has called the destructive methods {@link #dispose()} or {@link #wipe()}. + *
    + * + * @author Bergmann Gabor + * @noextend This class is not intended to be subclassed by clients. + */ +public abstract class AdvancedViatraQueryEngine extends ViatraQueryEngine { + + /** + * Creates a new unmanaged VIATRA Query engine to evaluate queries over a given scope specified by an {@link QueryScope}. + * + *

    Repeated invocations will return different instances, so other clients are unable to independently access + * and influence the returned engine. Note that unmanaged engines do not benefit from some performance improvements + * that stem from sharing incrementally maintained indices and caches between multiple clients using the same managed + * engine instance. + * + *

    + * Client is responsible for the lifecycle of the returned engine, hence the usage of the advanced interface + * {@link AdvancedViatraQueryEngine}. + * + *

    + * The match set of any patterns will be incrementally refreshed upon updates from this scope. + * + * @param scope + * the scope of query evaluation; the definition of the set of model elements that this engine is operates on. + * Provide e.g. a {@link EMFScope} for evaluating queries on an EMF model. + * @return the advanced interface to a newly created unmanaged engine + * @since 0.9 + */ + public static AdvancedViatraQueryEngine createUnmanagedEngine(QueryScope scope) { + return new ViatraQueryEngineImpl(null, scope); + } + + /** + * Creates a new unmanaged VIATRA Query engine to evaluate queries over a given scope specified by an {@link QueryScope}. + * + *

    Repeated invocations will return different instances, so other clients are unable to independently access + * and influence the returned engine. Note that unmanaged engines do not benefit from some performance improvements + * that stem from sharing incrementally maintained indices and caches between multiple clients using the same managed + * engine instance. + * + *

    + * Client is responsible for the lifecycle of the returned engine, hence the usage of the advanced interface + * {@link AdvancedViatraQueryEngine}. + * + *

    + * The match set of any patterns will be incrementally refreshed upon updates from this scope. + * + * @param scope + * the scope of query evaluation; the definition of the set of model elements that this engine is operates on. + * Provide e.g. a {@link EMFScope} for evaluating queries on an EMF model. + * @return the advanced interface to a newly created unmanaged engine + * @since 1.4 + */ + public static AdvancedViatraQueryEngine createUnmanagedEngine(QueryScope scope, ViatraQueryEngineOptions options) { + return new ViatraQueryEngineImpl(null, scope, options); + } + + /** + * Provides access to a given existing engine through the advanced interface. + * + *

    + * Caveat: if the referenced engine is managed (i.e. created via {@link ViatraQueryEngine#on(QueryScope)}), the advanced + * methods {@link #dispose()} and {@link #wipe()} will not be allowed. + * + * @param engine + * the engine to access using the advanced interface + * @return a reference to the same engine conforming to the advanced interface + */ + public static AdvancedViatraQueryEngine from(ViatraQueryEngine engine) { + return (AdvancedViatraQueryEngine) engine; + } + + /** + * Add an engine lifecycle listener to this engine instance. + * + * @param listener + * the {@link ViatraQueryEngineLifecycleListener} that should listen to lifecycle events from this engine + */ + public abstract void addLifecycleListener(ViatraQueryEngineLifecycleListener listener); + + /** + * Remove an existing lifecycle listener from this engine instance. + * + * @param listener + * the {@link ViatraQueryEngineLifecycleListener} that should not listen to lifecycle events from this + * engine anymore + */ + public abstract void removeLifecycleListener(ViatraQueryEngineLifecycleListener listener); + + /** + * Add an model update event listener to this engine instance (that fires its callbacks according to its + * notification level). + * + * @param listener + * the {@link ViatraQueryModelUpdateListener} that should listen to model update events from this engine. + */ + public abstract void addModelUpdateListener(ViatraQueryModelUpdateListener listener); + + /** + * Remove an existing model update event listener to this engine instance. + * + * @param listener + * the {@link ViatraQueryModelUpdateListener} that should not listen to model update events from this engine + * anymore + */ + public abstract void removeModelUpdateListener(ViatraQueryModelUpdateListener listener); + + /** + * Registers low-level callbacks for match appearance and disappearance on this pattern matcher. + * + *

    + * Caution: This is a low-level callback that is invoked when the pattern matcher is not necessarily in a + * consistent state yet. Importantly, no model modification permitted during the callback. Most users should use the + * databinding support ({@link org.eclipse.viatra.addon.databinding.runtime.api.ViatraObservables ViatraObservables}) or the event-driven API + * ({@link org.eclipse.viatra.transformation.evm.api.EventDrivenVM EventDrivenVM}) instead. + * + *

    + * Performance note: expected to be much more efficient than polling at {@link #addCallbackAfterUpdates(Runnable)}, + * but prone to "signal hazards", e.g. spurious match appearances that will disappear immediately afterwards. + * + *

    + * The callback can be unregistered via {@link #removeCallbackOnMatchUpdate(IMatchUpdateListener)}. + * + * @param fireNow + * if true, appearCallback will be immediately invoked on all current matches as a one-time effect. See + * also {@link ViatraQueryMatcher#forEachMatch(IMatchProcessor)}. + * @param listener + * the listener that will be notified of each new match that appears or disappears, starting from now. + * @param matcher + * the {@link ViatraQueryMatcher} for which this listener should be active + */ + public abstract void addMatchUpdateListener(ViatraQueryMatcher matcher, + IMatchUpdateListener listener, boolean fireNow); + + /** + * Remove an existing match update event listener to this engine instance. + * + * @param matcher + * the {@link ViatraQueryMatcher} for which this listener should not be active anymore + * @param listener + * the {@link IMatchUpdateListener} that should not receive the callbacks anymore + */ + public abstract void removeMatchUpdateListener(ViatraQueryMatcher matcher, + IMatchUpdateListener listener); + + + /** + * Access a pattern matcher based on a {@link IQuerySpecification}, overriding some of the default query evaluation hints. + * Multiple calls may return the same matcher depending on the actual evaluation hints. + * + *

    It is guaranteed that this method will always return a matcher instance which is functionally compatible + * with the requested functionality (see {@link IMatcherCapability}). + * Otherwise, the query evaluator is free to ignore any hints. + * + *

    For stateful query backends (Rete), hints may be effective only the first time a matcher is created. + * @param querySpecification a {@link IQuerySpecification} that describes a VIATRA query + * @return a pattern matcher corresponding to the specification + * @param optionalEvaluationHints additional / overriding options on query evaluation; passing null means default options associated with the query + * @throws ViatraQueryRuntimeException if the matcher could not be initialized + * @since 0.9 + */ + public abstract > Matcher getMatcher( + IQuerySpecification querySpecification, + QueryEvaluationHint optionalEvaluationHints); + + /** + * Initializes matchers for a group of patterns as one step (optionally overriding some of the default query evaluation hints). + * If some of the pattern matchers are already + * constructed in the engine, no task is performed for them. + * + *

    + * This preparation step has the advantage that it prepares pattern matchers for an arbitrary number of patterns in a + * single-pass traversal of the model. + * This is typically more efficient than traversing the model each time an individual pattern matcher is initialized on demand. + * The performance benefit only manifests itself if the engine is not in wildcard mode. + * + * @param queryGroup a {@link IQueryGroup} identifying a set of VIATRA queries + * @param optionalEvaluationHints additional / overriding options on query evaluation; passing null means default options associated with each query + * @throws ViatraQueryRuntimeException + * if there was an error in preparing the engine + * @since 0.9 + */ + public abstract void prepareGroup(IQueryGroup queryGroup, QueryEvaluationHint optionalEvaluationHints); + + /** + * Indicates whether the engine is managed, i.e. the default engine assigned to the given scope root by + * {@link ViatraQueryEngine#on(QueryScope)}. + * + *

    + * If the engine is managed, there may be other clients using it, as all calls to + * {@link ViatraQueryEngine#on(QueryScope)} return the same managed engine instance for a given scope root. Therefore the + * destructive methods {@link #wipe()} and {@link #dispose()} are not allowed. + * + *

    + * On the other hand, if the engine is unmanaged (i.e. a private instance created using + * {@link #createUnmanagedEngine(QueryScope)}), then {@link #wipe()} and {@link #dispose()} can be called. If you + * explicitly share a private, unmanaged engine between multiple sites, register a callback using + * {@link #addLifecycleListener(ViatraQueryEngineLifecycleListener)} to learn when another client has called these + * destructive methods. + * + * @return true if the engine is managed, and therefore potentially shared with other clients querying the same EMF + * model + */ + public abstract boolean isManaged(); + + /** + * Indicates whether the engine is in a tainted, inconsistent state due to some internal errors. If true, results + * are no longer reliable; engine should be disposed. + * + *

    + * The engine is in a tainted state if any of its internal processes report back a fatal error. The + * {@link ViatraQueryEngineLifecycleListener} interface provides a callback method for entering the tainted state. + * + * @return the tainted state + */ + public abstract boolean isTainted(); + + /** + * Discards any pattern matcher caches and forgets known patterns. The base index built directly on the underlying + * EMF model, however, is kept in memory to allow reuse when new pattern matchers are built. Use this method if you + * have e.g. new versions of the same patterns, to be matched on the same model. + * + *

    + * Matcher objects will continue to return stale results. If no references are retained to the matchers, they can + * eventually be GC'ed. + *

    + * Disallowed if the engine is managed (see {@link #isManaged()}), as there may be other clients using it. + *

    + * If you explicitly share a private, unmanaged engine between multiple sites, register a callback using + * {@link #addLifecycleListener(ViatraQueryEngineLifecycleListener)} to learn when another client has called this + * destructive method. + * + * @throws UnsupportedOperationException + * if engine is managed + */ + public abstract void wipe(); + + /** + * Completely disconnects and dismantles the engine. Cannot be reversed. + *

    + * Matcher objects will continue to return stale results. If no references are retained to the matchers or the + * engine, they can eventually be GC'ed, and they won't block the EMF model from being GC'ed anymore. + *

    + * The base indexer (see {@link #getBaseIndex()}) built on the model will be disposed alongside the engine, unless + * the user has manually added listeners on the base index that were not removed yet. + *

    + * Disallowed if the engine is managed (see {@link #isManaged()}), as there may be other clients using it. + *

    + * If you explicitly share a private, unmanaged engine between multiple sites, register a callback using + * {@link #addLifecycleListener(ViatraQueryEngineLifecycleListener)} to learn when another client has called this + * destructive method. + * + * @throws UnsupportedOperationException + * if engine is managed + */ + public abstract void dispose(); + + /** + * Provides access to the selected query backend component of the VIATRA Query engine. + * @noreference for internal use only + * @throws ViatraQueryRuntimeException + */ + public abstract IQueryBackend getQueryBackend(IQueryBackendFactory iQueryBackendFactory); + + /** + * Access an existing pattern matcher based on a {@link IQuerySpecification}, and optional hints override. + * @param querySpecification a {@link IQuerySpecification} that describes a VIATRA query specification + * @param optionalOverrideHints a {@link QueryEvaluationHint} that may override the pattern hints (can be null) + * @return a pattern matcher corresponding to the specification, null if a matcher does not exist yet. + * @since 1.4 + */ + public abstract > Matcher getExistingMatcher(IQuerySpecification querySpecification, QueryEvaluationHint optionalOverrideHints); + + /** + * Returns the immutable {@link ViatraQueryEngineOptions} of the engine. + * + * @return the engine options + * @since 1.4 + */ + public abstract ViatraQueryEngineOptions getEngineOptions(); + + /** + * Return the underlying result provider for the given matcher. + * + * @beta This method may change in future versions + * @since 1.4 + * @noreference This method is considered internal API + */ + public abstract IQueryResultProvider getResultProviderOfMatcher(ViatraQueryMatcher matcher); + + /** + * The given callable will be executed, and all update propagation in stateful query backends + * will be delayed until the execution is done. Within the callback, these backends will provide stale results. + * + *

    It is optional for a {@link IQueryBackend} to support the delaying of update propagation; stateless backends will display up-to-date results. + * In this case, the given callable shall be executed, and the update propagation shall happen just like in non-delayed execution. + * + *

    Example: in the Rete network, no messages will be propagated until the given callable is executed. + * After the execution of the callable, all accumulated messages will be delivered. + * + *

    The purpose of this method is that stateful query backends may save work when multiple model modifications are performed within the callback that partially cancel each other out. + * + * @param callable the callable to be executed + * @return the result of the callable + * @since 1.6 + */ + public abstract V delayUpdatePropagation(Callable callable) throws InvocationTargetException; + + /** + * Returns true if the update propagation in this engine is currently delayed, false otherwise. + * + * @see {@link #delayUpdatePropagation(Callable)} + * @since 1.6 + */ + public abstract boolean isUpdatePropagationDelayed(); + + /** + * Returns true if the {@link #dispose()} method was called on this engine previously. + * @since 2.0 + */ + public abstract boolean isDisposed(); +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/GenericPatternMatch.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/GenericPatternMatch.java new file mode 100644 index 00000000..b4de2b70 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/GenericPatternMatch.java @@ -0,0 +1,166 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.api; + +import java.util.Arrays; + +import tools.refinery.viatra.runtime.api.impl.BasePatternMatch; + +/** + * Generic signature object implementation. + * + * See also the generated matcher and signature of the pattern, with pattern-specific API simplifications. + * + * @author Bergmann Gábor + * @since 0.9 + * + */ +public abstract class GenericPatternMatch extends BasePatternMatch { + + private final GenericQuerySpecification specification; + private final Object[] array; + + private GenericPatternMatch(GenericQuerySpecification specification, Object[] array) { + super(); + this.specification = specification; + this.array = array; + } + + @Override + public Object get(String parameterName) { + Integer index = specification.getPositionOfParameter(parameterName); + return index == null ? null : array[index]; + } + + @Override + public Object get(int position) { + return array[position]; + } + + @Override + public boolean set(String parameterName, Object newValue) { + if (!isMutable()) throw new UnsupportedOperationException(); + Integer index = specification.getPositionOfParameter(parameterName); + if (index == null) + return false; + array[index] = newValue; + return true; + } + + @Override + public Object[] toArray() { + return Arrays.copyOf(array, array.length); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + for (int i = 0; i < array.length; ++i) + result = prime * result + ((array[i] == null) ? 0 : array[i].hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof GenericPatternMatch)) { // this should be infrequent + if (obj == null) + return false; + if (!(obj instanceof IPatternMatch)) + return false; + IPatternMatch other = (IPatternMatch) obj; + if (!specification().equals(other.specification())) + return false; + return Arrays.deepEquals(array, other.toArray()); + } + final GenericPatternMatch other = (GenericPatternMatch) obj; + return specification().equals(other.specification()) && Arrays.deepEquals(array, other.array); + } + + @Override + public String prettyPrint() { + StringBuilder result = new StringBuilder(); + for (int i = 0; i < array.length; ++i) { + if (i != 0) + result.append(", "); + result.append("\"" + parameterNames().get(i) + "\"=" + prettyPrintValue(array[i])); + } + return result.toString(); + } + + @Override + public GenericQuerySpecification specification() { + return specification; + } + + /** + * Returns an empty, mutable match. + * Fields of the mutable match can be filled to create a partial match, usable as matcher input. + * + * @return the empty match + */ + public static GenericPatternMatch newEmptyMatch(GenericQuerySpecification specification) { + return new Mutable(specification, new Object[specification.getParameters().size()]); + } + + /** + * Returns a mutable (partial) match. + * Fields of the mutable match can be filled to create a partial match, usable as matcher input. + * + * @param parameters + * the fixed value of pattern parameters, or null if not bound. + * @return the new, mutable (partial) match object. + */ + public static GenericPatternMatch newMutableMatch(GenericQuerySpecification specification, Object... parameters) { + return new Mutable(specification, parameters); + } + + /** + * Returns a new (partial) match. + * This can be used e.g. to call the matcher with a partial match. + * + *

    The returned match will be immutable. Use {@link #newEmptyMatch(GenericQuerySpecification)} to obtain a mutable match object. + * + * @param parameters + * the fixed value of pattern parameters, or null if not bound. + * @return the (partial) match object. + */ + public static GenericPatternMatch newMatch(GenericQuerySpecification specification, Object... parameters) { + return new Immutable(specification, Arrays.copyOf(parameters, parameters.length)); + } + + @Override + public IPatternMatch toImmutable() { + return isMutable() ? newMatch(specification, array) : this; + } + + static final class Mutable extends GenericPatternMatch { + Mutable(GenericQuerySpecification specification, Object[] array) { + super(specification, array); + } + + @Override + public boolean isMutable() { + return true; + } + } + static final class Immutable extends GenericPatternMatch { + Immutable(GenericQuerySpecification specification, Object[] array) { + super(specification, array); + } + + @Override + public boolean isMutable() { + return false; + } + } +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/GenericPatternMatcher.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/GenericPatternMatcher.java new file mode 100644 index 00000000..9a3fbb44 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/GenericPatternMatcher.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.api; + +import tools.refinery.viatra.runtime.api.impl.BaseMatcher; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; + +/** + * This is a generic pattern matcher for any VIATRA pattern, with "interpretative" query execution. + * To use the pattern matcher on a given model, obtain a {@link GenericQuerySpecification} first, then + * invoke e.g. {@link GenericQuerySpecification#getMatcher(ViatraQueryEngine)}. + * in conjunction with {@link ViatraQueryEngine#on(tools.refinery.viatra.runtime.api.scope.QueryScope)}. + *

    + * Whenever available, consider using the pattern-specific generated matcher API instead. + * + *

    + * Matches of the pattern will be represented as {@link GenericPatternMatch}. + * + * @author Bergmann Gábor + * @see GenericPatternMatch + * @see GenericMatchProcessor + * @see GenericQuerySpecification + * @since 0.9 + */ +public class GenericPatternMatcher extends BaseMatcher { + + /** + * @since 1.4 + */ + public GenericPatternMatcher(GenericQuerySpecification specification) { + super(specification); + } + + @Override + public GenericPatternMatch arrayToMatch(Object[] parameters) { + return GenericPatternMatch.newMatch(getSpecification(), parameters); + } + + @Override + public GenericPatternMatch arrayToMatchMutable(Object[] parameters) { + return GenericPatternMatch.newMutableMatch(getSpecification(), parameters); + } + + @Override + protected GenericPatternMatch tupleToMatch(Tuple t) { + return new GenericPatternMatch.Immutable(getSpecification(), /*avoid re-cloning*/t.getElements()); + } + + @SuppressWarnings("unchecked") + @Override + public GenericQuerySpecification getSpecification() { + return (GenericQuerySpecification)querySpecification; + } + + /** + * Internal method for {@link GenericQuerySpecification} + * @noreference + */ + static GenericPatternMatcher instantiate(GenericQuerySpecification querySpecification) { + return new GenericPatternMatcher(querySpecification); + } + + /** + * Internal method for {@link GenericQuerySpecification} + * @noreference + */ + static GenericPatternMatcher instantiate(ViatraQueryEngine engine, GenericQuerySpecification querySpecification) { + // check if matcher already exists + GenericPatternMatcher matcher = engine.getExistingMatcher(querySpecification); + if (matcher == null) { + matcher = engine.getMatcher(querySpecification); + } + return matcher; + } + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/GenericQueryGroup.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/GenericQueryGroup.java new file mode 100644 index 00000000..a5661bc9 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/GenericQueryGroup.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Mark Czotter, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.api; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import tools.refinery.viatra.runtime.api.impl.BaseQueryGroup; + +/** + * Generic implementation of {@link IQueryGroup}, covering an arbitrarily chosen set of patterns. Use the public + * constructor or static GenericQueryGroup.of(...) methods to instantiate. + * + * @author Mark Czotter + * + */ +public class GenericQueryGroup extends BaseQueryGroup { + + private final Set> patterns; + + /** + * Creates a GenericQueryGroup object with a set of patterns. + * + * @param patterns + */ + public GenericQueryGroup(Set> patterns) { + this.patterns = patterns; + } + + @Override + public Set> getSpecifications() { + return patterns; + } + + /** + * Creates a generic {@link IQueryGroup} instance from {@link IQuerySpecification} objects. + * + * @since 2.0 + */ + public static IQueryGroup of(Stream> querySpecifications) { + return new GenericQueryGroup(querySpecifications.collect(Collectors.toSet())); + } + + /** + * Creates a generic {@link IQueryGroup} instance from {@link IQuerySpecification} objects. + * + * @param querySpecifications + */ + public static IQueryGroup of(Set> querySpecifications) { + return new GenericQueryGroup(querySpecifications); + } + + /** + * Creates a generic {@link IQueryGroup} instance from {@link IQuerySpecification} objects. + * + * @param querySpecifications + */ + public static IQueryGroup of(IQuerySpecification... querySpecifications) { + return of(new HashSet>(Arrays.asList(querySpecifications))); + } + + /** + * Creates a generic {@link IQueryGroup} instance from other {@link IQueryGroup} objects (subgroups). + * + */ + public static IQueryGroup of(IQueryGroup... subGroups) { + Set> patterns = new HashSet>(); + for (IQueryGroup group : subGroups) { + patterns.addAll(group.getSpecifications()); + } + return new GenericQueryGroup(patterns); + } +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/GenericQuerySpecification.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/GenericQuerySpecification.java new file mode 100644 index 00000000..5681ac19 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/GenericQuerySpecification.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.api; + +import tools.refinery.viatra.runtime.api.impl.BaseQuerySpecification; +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PVisibility; + +/** + * This is a generic query specification for VIATRA pattern matchers, for "interpretative" query execution. + * Should be subclassed by query specification implementations specific to query languages. + * + *

    + * When available, consider using the pattern-specific generated matcher API instead. + * + *

    + * The created matcher will be of type {@link GenericPatternMatcher}. Matches of the pattern will be represented as + * {@link GenericPatternMatch}. + * + *

    + * Note for overriding (if you have your own query language or ): + * Derived classes should use {@link #defaultInstantiate(ViatraQueryEngine)} for implementing + * {@link #instantiate(ViatraQueryEngine)} if they use {@link GenericPatternMatcher} proper. + * + * @see GenericPatternMatcher + * @see GenericPatternMatch + * @see GenericMatchProcessor + * @author Bergmann Gábor + * @noinstantiate This class is not intended to be instantiated by end-users. + * @since 0.9 + */ +public abstract class GenericQuerySpecification extends + BaseQuerySpecification { + + /** + * Instantiates query specification for the given internal query representation. + */ + public GenericQuerySpecification(PQuery wrappedPQuery) { + super(wrappedPQuery); + } + + @Override + public GenericPatternMatch newEmptyMatch() { + return GenericPatternMatch.newEmptyMatch(this); + } + + @Override + public GenericPatternMatch newMatch(Object... parameters) { + return GenericPatternMatch.newMatch(this, parameters); + } + + /** + * Derived classes should use this implementation of {@link #instantiate(ViatraQueryEngine)} + * if they use {@link GenericPatternMatcher} proper. + * @throws ViatraQueryRuntimeException + */ + protected GenericPatternMatcher defaultInstantiate(ViatraQueryEngine engine) { + return GenericPatternMatcher.instantiate(engine, this); + } + + /** + * @since 2.0 + */ + @Override + public PVisibility getVisibility() { + return getInternalQueryRepresentation().getVisibility(); + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IMatchUpdateListener.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IMatchUpdateListener.java new file mode 100644 index 00000000..23c64537 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IMatchUpdateListener.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.api; + +/** + * An interface for low-level notifications about match appearance and disappearance. + * + *

    + * See {@link ViatraQueryMatcher#addCallbackOnMatchUpdate(IMatchUpdateListener, boolean)} for usage. Clients should + * consider using {@link MatchUpdateAdapter} or deriving their implementation from it. + * + * @author Bergmann Gabor + * + */ +public interface IMatchUpdateListener { + /** + * Will be invoked on each new match that appears. + * + * @param match + * the match that has just appeared. + */ + public void notifyAppearance(Match match); + + /** + * Will be invoked on each existing match that disappears. + * + * @param match + * the match that has just disappeared. + */ + public void notifyDisappearance(Match match); +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IModelConnectorTypeEnum.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IModelConnectorTypeEnum.java new file mode 100644 index 00000000..e672a6e2 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IModelConnectorTypeEnum.java @@ -0,0 +1,18 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Andras Okros, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.api; + +/** + * This enum represents the possible notifier types which a model input should provide for the ui. + */ +public enum IModelConnectorTypeEnum { + + RESOURCESET, RESOURCE; + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IPatternMatch.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IPatternMatch.java new file mode 100644 index 00000000..be6467cc --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IPatternMatch.java @@ -0,0 +1,107 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.api; + +import java.util.List; + +/** + * Generic interface for a single match of a pattern. Each instance is a (partial) substitution of pattern parameters, + * essentially a parameter to value mapping. + * + *

    Can also represent a partial match; unsubstituted parameters are assigned to null. Pattern matchers must never return + * a partial match, but they accept partial matches as method parameters. + * + * @author Bergmann Gábor + */ +public interface IPatternMatch extends Cloneable /* , Map */{ + /** @return the pattern for which this is a match. */ + public IQuerySpecification> specification(); + + /** Identifies the name of the pattern for which this is a match. */ + public String patternName(); + + /** Returns the list of symbolic parameter names. */ + public List parameterNames(); + + /** Returns the value of the parameter with the given name, or null if name is invalid. */ + public Object get(String parameterName); + + /** Returns the value of the parameter at the given position, or null if position is invalid. */ + public Object get(int position); + + /** + * Sets the parameter with the given name to the given value. + * + *

    Works only if match is mutable. See {@link #isMutable()}. + * + * @returns true if successful, false if parameter name is invalid. May also fail and return false if the value type + * is incompatible. + * @throws UnsupportedOperationException if match is not mutable. + */ + public boolean set(String parameterName, Object newValue); + + /** + * Sets the parameter at the given position to the given value. + * + *

    Works only if match is mutable. See {@link #isMutable()}. + * + * @returns true if successful, false if position is invalid. May also fail and return false if the value type is + * incompatible. + * @throws UnsupportedOperationException if match is not mutable. + */ + public boolean set(int position, Object newValue); + + /** + * Returns whether the match object can be further modified after its creation. Setters work only if the match is mutable. + * + *

    Matches computed by the pattern matchers are not mutable, so that the match set cannot be modified externally. + * Partial matches used as matcher input, however, can be mutable; such match objects can be created using {@link ViatraQueryMatcher#newEmptyMatch()}. + * + * @return whether the match can be modified + */ + public boolean isMutable(); + + /** + * Converts the match to an array representation, with each pattern parameter at their respective position. + * In case of a partial match, unsubstituted parameters will be represented as null elements in the array. + * + * @return a newly constructed array containing each parameter substitution of the match in order. + */ + public Object[] toArray(); + + /** + * Takes an immutable snapshot of this match. + * @return the match itself in case of immutable matches, an immutable copy in case of mutable ones. + */ + public IPatternMatch toImmutable(); + + /** Prints the list of parameter-value pairs. */ + public String prettyPrint(); + + /** + * Checks that this match is compatible with the given other match. + * This is used for filtering the match set of a matcher. + * + *

    Two non-null matches are compatible if and only if: + *

      + *
    • They share the same pattern.
    • + *
    • For each parameter, where they are set (non-null) in both matches, + * their values are equal.
    • + * + *
    + * + *

    Furthermore, all matches are considered compatible with + * null matches (e.g. empty filter). + * + * @param other + * @return true, if this is compatible with other, or other is null + */ + public boolean isCompatibleWith(IPatternMatch other); +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IQueryGroup.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IQueryGroup.java new file mode 100644 index 00000000..a783f823 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IQueryGroup.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Mark Czotter, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.api; + +import java.util.Set; + +/** + * Generic interface for group of query specifications. + * + *

    It handles more than one patterns as a group, and provides functionality to initialize the matchers together (which + * has performance benefits). + * + * @author Mark Czotter + * + */ +public interface IQueryGroup { + + /** + * Initializes matchers for the group of patterns within an {@link ViatraQueryEngine}. If some of the pattern matchers are already + * constructed in the engine, no task is performed for them. + * + *

    + * This preparation step has the advantage that it prepares pattern matchers for an arbitrary number of patterns in a + * single-pass traversal of the model. + * This is typically more efficient than traversing the model each time an individual pattern matcher is initialized on demand. + * The performance benefit only manifests itself if the engine is not in wildcard mode. + * + * @param engine + * the existing VIATRA Query engine in which the matchers will be created. + * @throws ViatraQueryRuntimeException + * if there was an error in preparing the engine + */ + public void prepare(ViatraQueryEngine engine); + + /** + * Returns the currently assigned {@link IQuerySpecification}s. + */ + public Set> getSpecifications(); + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IQuerySpecification.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IQuerySpecification.java new file mode 100644 index 00000000..9b84b031 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IQuerySpecification.java @@ -0,0 +1,87 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.api; + +import tools.refinery.viatra.runtime.api.scope.QueryScope; +import tools.refinery.viatra.runtime.emf.EMFScope; +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQueryHeader; + +/** + * API interface for a VIATRA query specification. Each query is associated with a pattern. Methods instantiate a matcher + * of the pattern with various parameters. + * + *

    As of 0.9.0, some internal details (mostly relevant for query evaluator backends) have been moved to {@link #getInternalQueryRepresentation()}. + * + * @author Bergmann Gábor + * + */ +public interface IQuerySpecification> extends PQueryHeader { + + /** + * Initializes the pattern matcher within an existing {@link ViatraQueryEngine}. If the pattern matcher is already + * constructed in the engine, only a lightweight reference is created. + *

    + * The match set will be incrementally refreshed upon updates. + * + * @param engine + * the existing VIATRA Query engine in which this matcher will be created. + * @throws ViatraQueryRuntimeException + * if an error occurs during pattern matcher creation + */ + public Matcher getMatcher(ViatraQueryEngine engine); + + + /** + * Returns an empty, mutable Match compatible with matchers of this query. + * Fields of the mutable match can be filled to create a partial match, usable as matcher input. + * This can be used to call the matcher with a partial match + * even if the specific class of the matcher or the match is unknown. + * + * @return the empty match + */ + public abstract IPatternMatch newEmptyMatch(); + + /** + * Returns a new (partial) Match object compatible with matchers of this query. + * This can be used e.g. to call the matcher with a partial + * match. + * + *

    The returned match will be immutable. Use {@link #newEmptyMatch()} to obtain a mutable match object. + * + * @param parameters + * the fixed value of pattern parameters, or null if not bound. + * @return the (partial) match object. + */ + public abstract IPatternMatch newMatch(Object... parameters); + + /** + * The query is formulated over this kind of modeling platform. + * E.g. for queries over EMF models, the {@link EMFScope} class is returned. + */ + public Class getPreferredScopeClass(); + + /** + * Returns the definition of the query in a format intended for consumption by the query evaluator. + * @return the internal representation of the query. + */ + public PQuery getInternalQueryRepresentation(); + + /** + * Creates a new uninitialized matcher, which is not functional until an engine initializes it. Clients + * should not call this method, it is used by the {@link ViatraQueryEngine} instance to instantiate matchers. + * @throws ViatraQueryRuntimeException + * @noreference This method is not intended to be referenced by clients. + * @since 1.4 + */ + public Matcher instantiate(); + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IRunOnceQueryEngine.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IRunOnceQueryEngine.java new file mode 100644 index 00000000..b625980b --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/IRunOnceQueryEngine.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Abel Hegedus, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.api; + +import java.util.Collection; + +import org.eclipse.emf.common.notify.Notifier; +import tools.refinery.viatra.runtime.base.api.BaseIndexOptions; +import tools.refinery.viatra.runtime.base.api.NavigationHelper; + +/** + * A run-once query engine is used to get matches for queries without incremental support. + * Users can create a query engine with a given {@link Notifier} as scope and use a query specification + * to retrieve the current match set with this scope (see {@link #getAllMatches}). + * + * @author Abel Hegedus + * + */ +public interface IRunOnceQueryEngine { + + /** + * Returns the set of all matches for the given query in the scope of the engine. + * + * @param querySpecification the query that is evaluated + * @return matches represented as a Match object. + */ + Collection getAllMatches( + final IQuerySpecification> querySpecification); + + /** + * @return the scope of pattern matching, i.e. the root of the EMF model tree that this engine is attached to. + */ + Notifier getScope(); + + /** + * The base index options specifies how the base index is built, including wildcard mode (defaults to false) and + * dynamic EMF mode (defaults to false). See {@link NavigationHelper} for the explanation of wildcard mode and + * dynamic EMF mode. + * + *

    The returned options can be modified in order to affect subsequent calls of {@link #getAllMatches}. + * + * @return the base index options used by the engine. + */ + BaseIndexOptions getBaseIndexOptions(); + + /** + * When set to true, the run-once query engine will not dispose it's engine and will resample the values of derived + * features before returning matches if the model changed since the last call. + * + * If the values of derived features may change without any model modification, call {@link #resampleOnNextCall()} + * before subsequent calls of {@link #getAllMatches}. + * + * @param automaticResampling + */ + void setAutomaticResampling(boolean automaticResampling); + + /** + * If automatic resampling is enabled and the value of derived features may change without model modifications, + * calling this method will make sure that re-sampling will occur before returning match results. + */ + void resampleOnNextCall(); +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/LazyLoadingQueryGroup.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/LazyLoadingQueryGroup.java new file mode 100644 index 00000000..6ae44b7c --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/LazyLoadingQueryGroup.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.api; + +import java.util.Collections; +import java.util.Objects; +import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import tools.refinery.viatra.runtime.api.impl.BaseQueryGroup; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; +import tools.refinery.viatra.runtime.util.ViatraQueryLoggingUtil; + +/** + * Initializes a query group from a set of query providers. The query providers are not executed until the queries + * themselves are asked in the {@link #getSpecifications()} method. + * + * @author Zoltan Ujhelyi + * @since 1.3 + * + */ +public class LazyLoadingQueryGroup extends BaseQueryGroup { + + private final Set>> providers; + private Set> specifications = null; + + /** + * @param providers a non-null set to initialize the group + */ + public LazyLoadingQueryGroup(Set>> providers) { + Preconditions.checkArgument(providers != null, "The set of providers must not be null"); + this.providers = providers; + } + + /** + * @param providers a non-null set to initialize the group + */ + public static IQueryGroup of(Set>> querySpecifications) { + return new LazyLoadingQueryGroup(querySpecifications); + } + + @Override + public Set> getSpecifications() { + if (specifications == null) { + try { + specifications = providers.stream().filter(Objects::nonNull).map(Supplier::get).filter(Objects::nonNull).collect(Collectors.toSet()); + } catch (Exception e) { + // TODO maybe store in issue list and provide better error reporting in general + String errorMessage = "Exception occurred while accessing query specification from provider: " + e.getMessage(); + ViatraQueryLoggingUtil.getLogger(getClass()).error(errorMessage); + return Collections.emptySet(); + } + } + return specifications; + } + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/MatchUpdateAdapter.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/MatchUpdateAdapter.java new file mode 100644 index 00000000..7de6d2c6 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/MatchUpdateAdapter.java @@ -0,0 +1,105 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.api; + +import java.util.function.Consumer; + +/** + * A default implementation of {@link IMatchUpdateListener} that contains two match processors, one for appearance, one + * for disappearance. Any of the two can be null; in this case, corresponding notifications will be ignored. + * + *

    + * Instantiate using either constructor. + * + * @author Bergmann Gabor + * + */ +public class MatchUpdateAdapter implements IMatchUpdateListener { + + Consumer appearCallback; + Consumer disappearCallback; + + /** + * Constructs an instance without any match processors registered yet. + * + * Use {@link #setAppearCallback(Consumer)} and {@link #setDisappearCallback(Consumer)} to specify + * optional match processors for match appearance and disappearance, respectively. + */ + public MatchUpdateAdapter() { + super(); + } + + /** + * Constructs an instance by specifying match processors. + * + * @param appearCallback + * a match processor that will be invoked on each new match that appears. If null, no callback will be + * executed on match appearance. See {@link Consumer} for details on how to implement. + * @param disappearCallback + * a match processor that will be invoked on each existing match that disappears. If null, no callback + * will be executed on match disappearance. See {@link Consumer} for details on how to implement. + * @since 2.0 + */ + public MatchUpdateAdapter(Consumer appearCallback, Consumer disappearCallback) { + super(); + setAppearCallback(appearCallback); + setDisappearCallback(disappearCallback); + } + + /** + * @return the match processor that will be invoked on each new match that appears. If null, no callback will be + * executed on match appearance. + * @since 2.0 + */ + public Consumer getAppearCallback() { + return appearCallback; + } + + /** + * @param appearCallback + * a match processor that will be invoked on each new match that appears. If null, no callback will be + * executed on match appearance. See {@link Consumer} for details on how to implement. + * @since 2.0 + */ + public void setAppearCallback(Consumer appearCallback) { + this.appearCallback = appearCallback; + } + + /** + * @return the match processor that will be invoked on each existing match that disappears. If null, no callback + * will be executed on match disappearance. + * @since 2.0 + */ + public Consumer getDisappearCallback() { + return disappearCallback; + } + + /** + * @param disappearCallback + * a match processor that will be invoked on each existing match that disappears. If null, no callback + * will be executed on match disappearance. See {@link Consumer} for details on how to implement. + * @since 2.0 + */ + public void setDisappearCallback(Consumer disappearCallback) { + this.disappearCallback = disappearCallback; + } + + @Override + public void notifyAppearance(Match match) { + if (appearCallback != null) + appearCallback.accept(match); + } + + @Override + public void notifyDisappearance(Match match) { + if (disappearCallback != null) + disappearCallback.accept(match); + } + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/PackageBasedQueryGroup.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/PackageBasedQueryGroup.java new file mode 100644 index 00000000..252bb7fd --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/PackageBasedQueryGroup.java @@ -0,0 +1,133 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Abel Hegedus, Mark Czotter, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.api; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import tools.refinery.viatra.runtime.api.impl.BaseQueryGroup; +import tools.refinery.viatra.runtime.registry.IQuerySpecificationRegistry; +import tools.refinery.viatra.runtime.registry.IQuerySpecificationRegistryEntry; +import tools.refinery.viatra.runtime.registry.IQuerySpecificationRegistryChangeListener; +import tools.refinery.viatra.runtime.registry.IRegistryView; +import tools.refinery.viatra.runtime.registry.IRegistryViewFilter; +import tools.refinery.viatra.runtime.registry.QuerySpecificationRegistry; + +/** + * Package based {@link BaseQueryGroup} implementation. It handles patterns as a group within the same package. + * + * @author Abel Hegedus, Mark Czotter + * + */ +public class PackageBasedQueryGroup extends BaseQueryGroup { + + private final Set> querySpecifications = new HashSet<>(); + private final String packageName; + private final boolean includeSubPackages; + private IRegistryView view; + + /** + * Creates a query group with specifications of a given package from the {@link QuerySpecificationRegistry}. Only + * query specifications with the exact package fully qualified name are included. + * + * @param packageName + * that contains the specifications + */ + public PackageBasedQueryGroup(String packageName) { + this(packageName, false); + } + + /** + * Creates a query group with specifications of a given package from the {@link QuerySpecificationRegistry}. + * + * @param packageName + * that contain the specifications + * @param includeSubPackages + * if true all query specifications with package names starting with the given package are included + */ + public PackageBasedQueryGroup(String packageName, boolean includeSubPackages) { + super(); + this.packageName = packageName; + this.includeSubPackages = includeSubPackages; + IQuerySpecificationRegistry registry = QuerySpecificationRegistry.getInstance(); + view = registry.createView(new PackageNameBasedViewFilter()); + for (IQuerySpecificationRegistryEntry entry : view.getEntries()) { + this.querySpecifications.add(entry.get()); + } + SpecificationSetUpdater listener = new SpecificationSetUpdater(); + view.addViewListener(listener); + } + + @Override + public Set> getSpecifications() { + return Collections.unmodifiableSet(new HashSet<>(querySpecifications)); + } + + public String getPackageName() { + return packageName; + } + + public boolean isIncludeSubPackages() { + return includeSubPackages; + } + + /** + * Refreshes the pattern group from the query specification registry based on the parameters used during the + * initialization. + */ + public void refresh() { + // do nothing, view is automatically refreshed + } + + /** + * Listener to update the specification set + * + * @author Abel Hegedus + * + */ + private final class SpecificationSetUpdater implements IQuerySpecificationRegistryChangeListener { + @Override + public void entryAdded(IQuerySpecificationRegistryEntry entry) { + querySpecifications.add(entry.get()); + } + + @Override + public void entryRemoved(IQuerySpecificationRegistryEntry entry) { + querySpecifications.remove(entry.get()); + } + } + + /** + * Registry view filter that checks FQNs against the given package name. + * + * @author Abel Hegedus + * + */ + private final class PackageNameBasedViewFilter implements IRegistryViewFilter { + @Override + public boolean isEntryRelevant(IQuerySpecificationRegistryEntry entry) { + String fqn = entry.getFullyQualifiedName(); + if (packageName.length() + 1 < fqn.length()) { + if (includeSubPackages) { + if (fqn.startsWith(packageName + '.')) { + return true; + } + } else { + String name = fqn.substring(fqn.lastIndexOf('.') + 1, fqn.length()); + if (fqn.equals(packageName + '.' + name)) { + return true; + } + } + } + return false; + } + } + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngine.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngine.java new file mode 100644 index 00000000..fd8ff848 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngine.java @@ -0,0 +1,152 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.api; + +import org.eclipse.emf.common.notify.Notifier; +import tools.refinery.viatra.runtime.api.scope.IBaseIndex; +import tools.refinery.viatra.runtime.api.scope.QueryScope; +import tools.refinery.viatra.runtime.base.api.BaseIndexOptions; +import tools.refinery.viatra.runtime.emf.EMFScope; +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; + +import java.util.Set; +import java.util.stream.Collectors; + +/** + * A Viatra Query (incremental) evaluation engine, attached to a model such as an EMF resource. The engine hosts pattern matchers, and + * will listen on model update notifications stemming from the given model in order to maintain live results. + * + *

    + * By default, ViatraQueryEngines do not need to be separately disposed; they will be garbage collected along with the model. + * Advanced users: see {@link AdvancedViatraQueryEngine} if you want fine control over the lifecycle of an engine. + * + *

    + * Pattern matchers within this engine may be instantiated in the following ways: + *

      + *
    • Recommended: instantiate the specific matcher class generated for the pattern by e.g. MyPatternMatcher.on(engine). + *
    • Use {@link #getMatcher(IQuerySpecification)} if the pattern-specific generated matcher API is not available. + *
    • Advanced: use the query specification associated with the generated matcher class to achieve the same. + *
    + * Additionally, a group of patterns (see {@link IQueryGroup}) can be initialized together before usage; this may improve + * the performance of pattern matcher construction by trying to gather all necessary information from the model in one go. + * Note that no such improvement is to be expected if the engine is specifically constructed in wildcard mode, + * an option available in some scope implementations + * (see {@link EMFScope#EMFScope(Notifier, BaseIndexOptions)} and {@link BaseIndexOptions#withWildcardMode(boolean)}). + * + * + * @author Bergmann Gábor + * @noextend This class is not intended to be subclassed by clients. + */ +public abstract class ViatraQueryEngine { + + + /** + * Obtain a (managed) {@link ViatraQueryEngine} to evaluate queries over a given scope specified by an {@link QueryScope}. + * + *

    For a given matcher scope, the same engine will be returned to any client. + * This facilitates the reuse of internal caches of the engine, greatly improving performance. + * + *

    The lifecycle of this engine is centrally managed, and will not be disposed as long as the model is retained in memory. + * The engine will be garbage collected along with the model. + * + *

    + * Advanced users: see {@link AdvancedViatraQueryEngine#createUnmanagedEngine(QueryScope)} to obtain a private, + * unmanaged engine that is not shared with other clients and allows tight control over its lifecycle. + * + * @param scope + * the scope of query evaluation; the definition of the set of model elements that this engine is operates on. + * Provide e.g. a {@link EMFScope} for evaluating queries on an EMF model. + * @return a (managed) {@link ViatraQueryEngine} instance + */ + public static ViatraQueryEngine on(QueryScope scope) { + return ViatraQueryEngineManager.getInstance().getQueryEngine(scope); + } + + /** + * Obtain a (managed) {@link ViatraQueryEngine} to evaluate queries over a given scope specified by an {@link QueryScope}. + * + *

    For a given matcher scope, the same engine will be returned to any client. + * This facilitates the reuse of internal caches of the engine, greatly improving performance. + * + *

    The lifecycle of this engine is centrally managed, and will not be disposed as long as the model is retained in memory. + * The engine will be garbage collected along with the model. + * + *

    + * Advanced users: see {@link AdvancedViatraQueryEngine#createUnmanagedEngine(QueryScope)} to obtain a private, + * unmanaged engine that is not shared with other clients and allows tight control over its lifecycle. + * + * @param scope + * the scope of query evaluation; the definition of the set of model elements that this engine is operates on. + * Provide e.g. a {@link EMFScope} for evaluating queries on an EMF model. + * @return a (managed) {@link ViatraQueryEngine} instance + * @since 1.4 + */ + public static ViatraQueryEngine on(QueryScope scope, ViatraQueryEngineOptions options) { + return ViatraQueryEngineManager.getInstance().getQueryEngine(scope, options); + } + + /** + * Provides access to the internal base index component of the engine, responsible for keeping track of basic + * contents of the model. + * + *

    If using an {@link EMFScope}, + * consider {@link EMFScope#extractUnderlyingEMFIndex(ViatraQueryEngine)} instead to access EMF-specific details. + * + * @return the baseIndex the NavigationHelper maintaining the base index + * @throws ViatraQueryRuntimeException + * if the base index could not be constructed + */ + public abstract IBaseIndex getBaseIndex(); + + /** + * Access a pattern matcher based on a {@link IQuerySpecification}. + * Multiple calls will return the same matcher. + * @param querySpecification a {@link IQuerySpecification} that describes a VIATRA query specification + * @return a pattern matcher corresponding to the specification + * @throws ViatraQueryRuntimeException if the matcher could not be initialized + */ + public abstract > Matcher getMatcher(IQuerySpecification querySpecification); + + /** + * Access a pattern matcher for the graph pattern with the given fully qualified name. + * Will succeed only if a query specification for this fully qualified name has been generated and registered. + * Multiple calls will return the same matcher unless the registered specification changes. + * + * @param patternFQN the fully qualified name of a VIATRA query specification + * @return a pattern matcher corresponding to the specification + * @throws ViatraQueryRuntimeException if the matcher could not be initialized + */ + public abstract ViatraQueryMatcher getMatcher(String patternFQN); + + /** + * Access an existing pattern matcher based on a {@link IQuerySpecification}. + * @param querySpecification a {@link IQuerySpecification} that describes a VIATRA query specification + * @return a pattern matcher corresponding to the specification, null if a matcher does not exist yet. + */ + public abstract > Matcher getExistingMatcher(IQuerySpecification querySpecification); + + + /** + * Access a copy of available {@link ViatraQueryMatcher} pattern matchers. + * @return a copy of the set of currently available pattern matchers registered on this engine instance + */ + public abstract Set> getCurrentMatchers(); + + public Set>> getRegisteredQuerySpecifications() { + return getCurrentMatchers().stream().map(ViatraQueryMatcher::getSpecification).collect(Collectors.toSet()); + } + + /** + * @return the scope of query evaluation; the definition of the set of model elements that this engine is operates on. + */ + public abstract QueryScope getScope(); + + public abstract void flushChanges(); +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngineInitializationListener.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngineInitializationListener.java new file mode 100644 index 00000000..02162a65 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngineInitializationListener.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Abel Hegedus, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.api; + +/** + * Listener interface to get notifications when a new managed engine is initialized. + * + * @author Abel Hegedus + * + */ +public interface ViatraQueryEngineInitializationListener { + + /** + * Called when a managed engine is initialized in the EngineManager. + * + * @param engine the initialized engine + */ + void engineInitialized(AdvancedViatraQueryEngine engine); + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngineLifecycleListener.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngineLifecycleListener.java new file mode 100644 index 00000000..1c4dc264 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngineLifecycleListener.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Abel Hegedus, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.api; + + +/** + * Listener interface for getting notification on changes in an {@link ViatraQueryEngine}. + * + * You can use it to remove any other listeners that you attached to matchers or the engine, + * or to handle matchers that are initialized after you started using the engine. + * + * @author Abel Hegedus + * + */ +public interface ViatraQueryEngineLifecycleListener { + + // ------------------------------------------------------------------------------- + // MATCHERS (methods notifying on changes in the matchers available in the engine) + // ------------------------------------------------------------------------------- + + /** + * Called after a matcher is instantiated in the engine + * + * @param matcher the new matcher + */ + void matcherInstantiated(ViatraQueryMatcher matcher); + + // ------------------------------------------------------------------------- + // HEALTH (methods notifying on changes that affect the health of the engine + // ------------------------------------------------------------------------- + + /** + * Called after the engine has become tainted due to a fatal error + */ + void engineBecameTainted(String message, Throwable t); + + /** + * Called after the engine has been wiped + */ + void engineWiped(); + + /** + * Called after the engine has been disposed + */ + void engineDisposed(); +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngineManager.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngineManager.java new file mode 100644 index 00000000..ca709b02 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngineManager.java @@ -0,0 +1,196 @@ +/******************************************************************************* + * Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.api; + +import static tools.refinery.viatra.runtime.matchers.util.Preconditions.checkArgument; + +import java.lang.ref.WeakReference; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; + +import tools.refinery.viatra.runtime.api.scope.QueryScope; +import tools.refinery.viatra.runtime.emf.EMFScope; +import tools.refinery.viatra.runtime.internal.apiimpl.ViatraQueryEngineImpl; +import tools.refinery.viatra.runtime.util.ViatraQueryLoggingUtil; + +/** + * Global registry of active VIATRA Query Engines. + * + *

    + * Manages an {@link ViatraQueryEngine} for each model (more precisely scope), that is created on demand. Managed engines are shared between + * clients querying the same model. + * + *

    + * It is also possible to create private, unmanaged engines that are not shared between clients. + * + *

    + * Only weak references are retained on the managed engines. So if there are no other references to the matchers or the + * engine, they can eventually be GC'ed, and they won't block the model from being GC'ed either. + * + * + * @author Bergmann Gabor + * + */ +public class ViatraQueryEngineManager { + private static ViatraQueryEngineManager instance = new ViatraQueryEngineManager(); + + + /** + * @return the singleton instance + */ + public static ViatraQueryEngineManager getInstance() { + return instance; + } + + /** + * The engine manager keeps track of the managed engines via weak references only, so it will not keep unused + * managed engines from being garbage collected. Still, as long as the user keeps the model in memory, the + * associated managed engine will not be garbage collected, as it is expected to be strongly reachable from the + * model via the scope-specific base index or change notification listeners (see + * {@link BaseIndexListener#iqEngine}). + */ + Map> engines; + + ViatraQueryEngineManager() { + super(); + engines = new WeakHashMap>(); + initializationListeners = new HashSet(); + } + + /** + * Creates a managed VIATRA Query Engine at a given scope (e.g. an EMF Resource or ResourceSet, as in {@link EMFScope}) + * or retrieves an already existing one. Repeated invocations for a single model root will return the same engine. + * Consequently, the engine will be reused between different clients querying the same model, providing performance benefits. + * + *

    + * The match set of any patterns will be incrementally refreshed upon updates from this scope. + * + * @param scope + * the scope of query evaluation; the definition of the set of model elements that this engine is operates on. + * Provide e.g. a {@link EMFScope} for evaluating queries on an EMF model. + * @return a new or previously existing engine + */ + public ViatraQueryEngine getQueryEngine(QueryScope scope) { + return getQueryEngine(scope, ViatraQueryEngineOptions.getDefault()); + } + + /** + * Creates a managed VIATRA Query Engine at a given scope (e.g. an EMF Resource or ResourceSet, as in {@link EMFScope}) + * or retrieves an already existing one. Repeated invocations for a single model root will return the same engine. + * Consequently, the engine will be reused between different clients querying the same model, providing performance benefits. + * + *

    + * The match set of any patterns will be incrementally refreshed upon updates from this scope. + * + * @param scope + * the scope of query evaluation; the definition of the set of model elements that this engine is operates on. + * Provide e.g. a {@link EMFScope} for evaluating queries on an EMF model. + * @return a new or previously existing engine + * @since 1.4 + */ + public ViatraQueryEngine getQueryEngine(QueryScope scope, ViatraQueryEngineOptions options) { + ViatraQueryEngineImpl engine = getEngineInternal(scope); + if (engine == null) { + engine = new ViatraQueryEngineImpl(this, scope, options); + engines.put(scope, new WeakReference(engine)); + notifyInitializationListeners(engine); + } + return engine; + } + + /** + * Retrieves an already existing managed VIATRA Query Engine. + * + * @param scope + * the scope of query evaluation; the definition of the set of model elements that this engine is operates on. + * Provide e.g. a {@link EMFScope} for evaluating queries on an EMF model. + * @return a previously existing engine, or null if no engine is active for the given EMF model root + */ + public ViatraQueryEngine getQueryEngineIfExists(QueryScope scope) { + return getEngineInternal(scope); + } + + /** + * Collects all {@link ViatraQueryEngine} instances that still exist. + * + * @return set of engines if there is any, otherwise EMTPY_SET + */ + public Set getExistingQueryEngines(){ + Set existingEngines = null; + for (WeakReference engineRef : engines.values()) { + AdvancedViatraQueryEngine engine = engineRef == null ? null : engineRef.get(); + if(existingEngines == null) { + existingEngines = new HashSet<>(); + } + existingEngines.add(engine); + } + if(existingEngines == null) { + existingEngines = Collections.emptySet(); + } + return existingEngines; + } + + private final Set initializationListeners; + + /** + * Registers a listener for new engine initialization. + * + *

    For removal, use {@link #removeQueryEngineInitializationListener} + * + * @param listener the listener to register + */ + public void addQueryEngineInitializationListener(ViatraQueryEngineInitializationListener listener) { + checkArgument(listener != null, "Cannot add null listener!"); + initializationListeners.add(listener); + } + + /** + * Removes a registered listener added with {@link #addQueryEngineInitializationListener} + * + * @param listener + */ + public void removeQueryEngineInitializationListener(ViatraQueryEngineInitializationListener listener) { + checkArgument(listener != null, "Cannot remove null listener!"); + initializationListeners.remove(listener); + } + + /** + * Notifies listeners that a new engine has been initialized. + * + * @param engine the initialized engine + */ + protected void notifyInitializationListeners(AdvancedViatraQueryEngine engine) { + try { + if (!initializationListeners.isEmpty()) { + for (ViatraQueryEngineInitializationListener listener : new HashSet<>(initializationListeners)) { + listener.engineInitialized(engine); + } + } + } catch (Exception ex) { + ViatraQueryLoggingUtil.getLogger(getClass()).fatal( + "VIATRA Query Engine Manager encountered an error in delivering notifications" + + " about engine initialization. ", ex); + } + } + + /** + * @param emfRoot + * @return + */ + private ViatraQueryEngineImpl getEngineInternal(QueryScope scope) { + final WeakReference engineRef = engines.get(scope); + ViatraQueryEngineImpl engine = engineRef == null ? null : engineRef.get(); + return engine; + } + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngineOptions.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngineOptions.java new file mode 100644 index 00000000..15bf1f91 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryEngineOptions.java @@ -0,0 +1,293 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Balázs Grill, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.api; + + +import java.util.Objects; +import java.util.ServiceLoader; + +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactory; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactoryProvider; +import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; + +/** + * This class is intended to provide options to a created {@link ViatraQueryEngine} instance. The {@link #DEFAULT} + * instance represents the configuration that is selected when no explicit options are provided by the user. To create + * new configurations, use the static builder methods {@link #defineOptions()} (starts with empty options) or + * {@link #copyOptions(ViatraQueryEngineOptions)} (starts with all options from an existing configuration). + * + * @author Balázs Grill, Zoltan Ujhelyi + * @since 1.4 + * + */ +public final class ViatraQueryEngineOptions { + + private static boolean areSystemDefaultsCalculated = false; + private static IQueryBackendFactory systemDefaultBackendFactory; + private static IQueryBackendFactory systemDefaultCachingBackendFactory; + private static IQueryBackendFactory systemDefaultSearchBackendFactory; + + /** + * @since 2.0 + */ + public static void setSystemDefaultBackends(IQueryBackendFactory systemDefaultBackendFactory, + IQueryBackendFactory systemDefaultCachingBackendFactory, + IQueryBackendFactory systemDefaultSearchBackendFactory) { + areSystemDefaultsCalculated = true; + ViatraQueryEngineOptions.systemDefaultBackendFactory = systemDefaultBackendFactory; + ViatraQueryEngineOptions.systemDefaultCachingBackendFactory = systemDefaultCachingBackendFactory; + ViatraQueryEngineOptions.systemDefaultSearchBackendFactory = systemDefaultSearchBackendFactory; + } + + /** + * If {@link #setSystemDefaultBackends(IQueryBackendFactory, IQueryBackendFactory, IQueryBackendFactory)} is not + * called, this method is responsible of finding the corresponding backends on the classpath using Java Service + * loaders. + */ + private static void calculateSystemDefaultBackends() { + for (IQueryBackendFactoryProvider provider : ServiceLoader.load(IQueryBackendFactoryProvider.class)) { + if (provider.isSystemDefaultEngine()) { + systemDefaultBackendFactory = provider.getFactory(); + } + if (provider.isSystemDefaultCachingBackend()) { + systemDefaultCachingBackendFactory = provider.getFactory(); + } + if (provider.isSystemDefaultSearchBackend()) { + systemDefaultSearchBackendFactory = provider.getFactory(); + } + } + areSystemDefaultsCalculated = true; + } + + private static IQueryBackendFactory getSystemDefaultBackend() { + if (!areSystemDefaultsCalculated) { + calculateSystemDefaultBackends(); + } + return Objects.requireNonNull(systemDefaultBackendFactory, "System default backend not found"); + } + + private static IQueryBackendFactory getSystemDefaultCachingBackend() { + if (!areSystemDefaultsCalculated) { + calculateSystemDefaultBackends(); + } + return Objects.requireNonNull(systemDefaultCachingBackendFactory, "System default caching backend not found"); + } + + private static IQueryBackendFactory getSystemDefaultSearchBackend() { + if (!areSystemDefaultsCalculated) { + calculateSystemDefaultBackends(); + } + return Objects.requireNonNull(systemDefaultSearchBackendFactory, "System default search backend not found"); + } + + private final QueryEvaluationHint engineDefaultHints; + + private final IQueryBackendFactory defaultCachingBackendFactory; + private final IQueryBackendFactory defaultSearchBackendFactory; + + /** The default engine options; if options are not defined, this version will be used. */ + private static ViatraQueryEngineOptions DEFAULT; + + /** + * @since 2.0 + */ + public static final ViatraQueryEngineOptions getDefault() { + if (DEFAULT == null) { + DEFAULT = new Builder().build(); + } + return DEFAULT; + } + + public static final class Builder { + private QueryEvaluationHint engineDefaultHints; + + private IQueryBackendFactory defaultBackendFactory; + private IQueryBackendFactory defaultCachingBackendFactory; + private IQueryBackendFactory defaultSearchBackendFactory; + + public Builder() { + + } + + public Builder(ViatraQueryEngineOptions from) { + this.engineDefaultHints = from.engineDefaultHints; + this.defaultBackendFactory = engineDefaultHints.getQueryBackendFactory(); + this.defaultCachingBackendFactory = from.defaultCachingBackendFactory; + this.defaultSearchBackendFactory = from.defaultSearchBackendFactory; + } + + /** + * Note that the backend factory in the hint is overridden by a factory added with + * {@link #withDefaultBackend(IQueryBackendFactory)}. + */ + public Builder withDefaultHint(QueryEvaluationHint engineDefaultHints) { + this.engineDefaultHints = engineDefaultHints; + return this; + } + + /** + * Note that this backend factory overrides the factory defined by the hint added by + * {@link #withDefaultHint(QueryEvaluationHint)}. + */ + public Builder withDefaultBackend(IQueryBackendFactory defaultBackendFactory) { + this.defaultBackendFactory = defaultBackendFactory; + return this; + } + + /** + * @since 2.0 + */ + public Builder withDefaultSearchBackend(IQueryBackendFactory defaultSearchBackendFactory) { + Preconditions.checkArgument(!defaultSearchBackendFactory.isCaching(), "%s is not a search backend", defaultSearchBackendFactory.getClass()); + this.defaultSearchBackendFactory = defaultSearchBackendFactory; + return this; + } + + public Builder withDefaultCachingBackend(IQueryBackendFactory defaultCachingBackendFactory) { + Preconditions.checkArgument(defaultCachingBackendFactory.isCaching(), "%s is not a caching backend", defaultCachingBackendFactory.getClass()); + this.defaultCachingBackendFactory = defaultCachingBackendFactory; + return this; + } + + public ViatraQueryEngineOptions build() { + IQueryBackendFactory defaultFactory = getDefaultBackend(); + QueryEvaluationHint hint = getEngineDefaultHints(defaultFactory); + return new ViatraQueryEngineOptions(hint, getDefaultCachingBackend(), getDefaultSearchBackend()); + } + + private IQueryBackendFactory getDefaultBackend() { + if (defaultBackendFactory != null){ + return defaultBackendFactory; + } else if (engineDefaultHints != null) { + return engineDefaultHints.getQueryBackendFactory(); + } else { + return getSystemDefaultBackend(); + } + } + + private IQueryBackendFactory getDefaultCachingBackend() { + if (defaultCachingBackendFactory != null) { + return defaultCachingBackendFactory; + } else if (defaultBackendFactory != null && defaultBackendFactory.isCaching()) { + return defaultBackendFactory; + } else { + return getSystemDefaultCachingBackend(); + } + } + + private IQueryBackendFactory getDefaultSearchBackend() { + if (defaultSearchBackendFactory != null) { + return defaultSearchBackendFactory; + } else if (defaultBackendFactory != null && !defaultBackendFactory.isCaching()) { + return defaultBackendFactory; + } else { + return getSystemDefaultSearchBackend(); + } + } + + private QueryEvaluationHint getEngineDefaultHints(IQueryBackendFactory defaultFactory) { + if (engineDefaultHints != null){ + return engineDefaultHints.overrideBy(new QueryEvaluationHint(null, defaultFactory)); + } else { + return new QueryEvaluationHint(null, defaultFactory); + } + } + } + + /** + * Initializes an option builder with no previously set options. + */ + public static Builder defineOptions() { + return new Builder(); + } + + /** + * Initializes an option builder with settings from an existing configuration. + */ + public static Builder copyOptions(ViatraQueryEngineOptions options) { + return new Builder(options); + } + + private ViatraQueryEngineOptions(QueryEvaluationHint engineDefaultHints, + IQueryBackendFactory defaultCachingBackendFactory, IQueryBackendFactory defaultSearchBackendFactory) { + this.engineDefaultHints = engineDefaultHints; + this.defaultCachingBackendFactory = defaultCachingBackendFactory; + this.defaultSearchBackendFactory = defaultSearchBackendFactory; + } + + public QueryEvaluationHint getEngineDefaultHints() { + return engineDefaultHints; + } + + /** + * Returns the configured default backend + * + * @return the defaultBackendFactory + */ + public IQueryBackendFactory getDefaultBackendFactory() { + switch (engineDefaultHints.getQueryBackendRequirementType()) { + case DEFAULT_CACHING: + return ViatraQueryEngineOptions.getSystemDefaultCachingBackend(); + case DEFAULT_SEARCH: + return ViatraQueryEngineOptions.getSystemDefaultCachingBackend(); + case SPECIFIC: + return engineDefaultHints.getQueryBackendFactory(); + case UNSPECIFIED: + default: + return ViatraQueryEngineOptions.getSystemDefaultBackend(); + } + } + + /** + * Returns the configured default caching backend. If the default backend caches matches, it is usually expected, but + * not mandatory for the two default backends to be the same. + */ + public IQueryBackendFactory getDefaultCachingBackendFactory() { + return defaultCachingBackendFactory; + } + + /** + * Returns the configured default search-based backend. If the default backend is search-based, it is usually expected, but + * not mandatory for the two default backends to be the same. + * @since 2.0 + */ + public IQueryBackendFactory getDefaultSearchBackendFactory() { + return defaultSearchBackendFactory; + } + + @Override + public String toString() { + // TODO defaultCachingBackendFactory is ignored + if(Objects.equals(engineDefaultHints, DEFAULT.engineDefaultHints)) + return "defaults"; + else + return engineDefaultHints.toString(); + } + + /** + * @since 2.0 + */ + public IQueryBackendFactory getQueryBackendFactory(QueryEvaluationHint hint) { + if (hint == null) { + return getDefaultBackendFactory(); + } + + switch (hint.getQueryBackendRequirementType()) { + case DEFAULT_CACHING: + return getDefaultCachingBackendFactory(); + case DEFAULT_SEARCH: + return getDefaultSearchBackendFactory(); + case SPECIFIC: + return hint.getQueryBackendFactory(); + default: + return getDefaultBackendFactory(); + } + } +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryMatcher.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryMatcher.java new file mode 100644 index 00000000..1ae0c96f --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryMatcher.java @@ -0,0 +1,258 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.api; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Stream; + +/** + * Interface for a VIATRA Query matcher associated with a graph pattern. + * + * @param + * the IPatternMatch type representing a single match of this pattern. + * @author Bergmann Gábor + * @noimplement This interface is not intended to be implemented by clients. Implement BaseMatcher instead. + */ +public interface ViatraQueryMatcher { + // REFLECTION + /** The pattern that will be matched. */ + IQuerySpecification> getSpecification(); + + /** Fully qualified name of the pattern. */ + String getPatternName(); + + /** Returns the index of the symbolic parameter with the given name. */ + Integer getPositionOfParameter(String parameterName); + + /** Returns the array of symbolic parameter names. */ + List getParameterNames(); + + // ALL MATCHES + /** + * Returns the set of all pattern matches. + * + * @return matches represented as a Match object. + */ + Collection getAllMatches(); + + /** + * Returns the set of all matches of the pattern that conform to the given fixed values of some parameters. + * + * @param partialMatch + * a partial match of the pattern where each non-null field binds the corresponding pattern parameter to + * a fixed value. + * @return matches represented as a Match object. + */ + Collection getAllMatches(Match partialMatch); + + /** + * Returns a stream of all pattern matches. + *

    + * WARNING If the result set changes while the stream is evaluated, the set of matches included in + * the stream are unspecified. In such cases, either rely on {@link #getAllMatches()} or collect the results of the + * stream in end-user code. + * + * @return matches represented as a Match object. + * @since 2.0 + */ + Stream streamAllMatches(); + + /** + * Returns a stream of all matches of the pattern that conform to the given fixed values of some parameters. + *

    + * WARNING If the result set changes while the stream is evaluated, the set of matches included in + * the stream are unspecified. In such cases, either rely on {@link #getAllMatches()} or collect the results of the + * stream in end-user code. + * + * @param partialMatch + * a partial match of the pattern where each non-null field binds the corresponding pattern parameter to + * a fixed value. + * @return matches represented as a Match object. + * @since 2.0 + */ + Stream streamAllMatches(Match partialMatch); + + // variant(s) with input binding as pattern-specific parameters: not declared in interface + + // SINGLE MATCH + /** + * Returns an arbitrarily chosen pattern match. Neither determinism nor randomness of selection is guaranteed. + * + * @return a match represented as a Match object, or an empty Optional if no match is found. + * @since 2.0 + */ + Optional getOneArbitraryMatch(); + + /** + * Returns an arbitrarily chosen match of the pattern that conforms to the given fixed values of some parameters. + * Neither determinism nor randomness of selection is guaranteed. + * + * @param partialMatch + * a partial match of the pattern where each non-null field binds the corresponding pattern parameter to + * a fixed value. + * @return a match represented as a Match object, or an empty Optional if no match is found. + * @since 2.0 + */ + Optional getOneArbitraryMatch(Match partialMatch); + + // variant(s) with input binding as pattern-specific parameters: not declared in interface + + // MATCH CHECKING + /** + * Indicates whether the query has any kind of matches. + * + * @return true if there exists a valid match of the pattern. + * @since 1.7 + */ + boolean hasMatch(); + + /** + * Indicates whether the given combination of specified pattern parameters constitute a valid pattern match, under + * any possible substitution of the unspecified parameters (if any). + * + * @param partialMatch + * a (partial) match of the pattern where each non-null field binds the corresponding pattern parameter + * to a fixed value. + * @return true if the input is a valid (partial) match of the pattern. + */ + boolean hasMatch(Match partialMatch); + + // variant(s) with input binding as pattern-specific parameters: not declared in interface + + // NUMBER OF MATCHES + /** + * Returns the number of all pattern matches. + * + * @return the number of pattern matches found. + */ + int countMatches(); + + /** + * Returns the number of all matches of the pattern that conform to the given fixed values of some parameters. + * + * @param partialMatch + * a partial match of the pattern where each non-null field binds the corresponding pattern parameter to + * a fixed value. + * @return the number of pattern matches found. + */ + int countMatches(Match partialMatch); + + // variant(s) with input binding as pattern-specific parameters: not declared in interface + + // FOR EACH MATCH + /** + * Executes the given processor on each match of the pattern. + * + * @param processor + * the action that will process each pattern match. + * @since 2.0 + */ + void forEachMatch(Consumer processor); + + /** + * Executes the given processor on each match of the pattern that conforms to the given fixed values of some + * parameters. + * + * @param partialMatch + * array where each non-null element binds the corresponding pattern parameter to a fixed value. + * @param processor + * the action that will process each pattern match. + * @since 2.0 + */ + void forEachMatch(Match partialMatch, Consumer processor); + + // variant(s) with input binding as pattern-specific parameters: not declared in interface + + // FOR ONE ARBITRARY MATCH + /** + * Executes the given processor on an arbitrarily chosen match of the pattern. Neither determinism nor randomness of + * selection is guaranteed. + * + * @param processor + * the action that will process the selected match. + * @return true if the pattern has at least one match, false if the processor was not invoked + * @since 2.0 + */ + boolean forOneArbitraryMatch(Consumer processor); + + /** + * Executes the given processor on an arbitrarily chosen match of the pattern that conforms to the given fixed + * values of some parameters. Neither determinism nor randomness of selection is guaranteed. + * + * @param partialMatch + * array where each non-null element binds the corresponding pattern parameter to a fixed value. + * @param processor + * the action that will process the selected match. + * @return true if the pattern has at least one match with the given parameter values, false if the processor was + * not invoked + * @since 2.0 + */ + boolean forOneArbitraryMatch(Match partialMatch, Consumer processor); + + // variant(s) with input binding as pattern-specific parameters: not declared in interface + + /** + * Returns an empty, mutable Match for the matcher. + * Fields of the mutable match can be filled to create a partial match, usable as matcher input. + * This can be used to call the matcher with a partial match + * even if the specific class of the matcher or the match is unknown. + * + * @return the empty match + */ + Match newEmptyMatch(); + + /** + * Returns a new (partial) Match object for the matcher. + * This can be used e.g. to call the matcher with a partial + * match. + * + *

    The returned match will be immutable. Use {@link #newEmptyMatch()} to obtain a mutable match object. + * + * @param parameters + * the fixed value of pattern parameters, or null if not bound. + * @return the (partial) match object. + */ + Match newMatch(Object... parameters); + + /** + * Retrieve the set of values that occur in matches for the given parameterName. + * + * @param parameterName + * name of the parameter for which values are returned + * @return the Set of all values for the given parameter, null if the parameter with the given name does not exists, + * empty set if there are no matches + */ + Set getAllValues(final String parameterName); + + /** + * Retrieve the set of values that occur in matches for the given parameterName, that conforms to the given fixed + * values of some parameters. + * + * @param parameterName + * name of the parameter for which values are returned + * @param partialMatch + * a partial match of the pattern where each non-null field binds the corresponding pattern parameter to + * a fixed value. + * @return the Set of all values for the given parameter, null if the parameter with the given name does not exists + * or if the parameter with the given name is set in partialMatch, empty set if there are no matches + */ + Set getAllValues(final String parameterName, Match partialMatch); + + /** + * Returns the engine that the matcher uses. + * + * @return the engine + */ + ViatraQueryEngine getEngine(); +} \ No newline at end of file diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryModelUpdateListener.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryModelUpdateListener.java new file mode 100644 index 00000000..da8bf87e --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/ViatraQueryModelUpdateListener.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Abel Hegedus, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.api; + + + +/** + * Listener interface for model changes affecting different levels of the VIATRA Query architecture. + * + * @author Abel Hegedus + * + */ +public interface ViatraQueryModelUpdateListener { + + /** + * Possible notification levels for changes + * + * @author Abel Hegedus + * + */ + enum ChangeLevel { + NO_CHANGE, MODEL, INDEX, MATCHSET; + + public ChangeLevel changeOccured(ChangeLevel occuredLevel) { + if(this.compareTo(occuredLevel) < 0) { + return occuredLevel; + } else { + return this; + } + } + } + /** + * Called after each change with also sending the level of change. + * Only called if the change level is at least at the level returned by getLevel(). + * + * @param changeLevel + */ + void notifyChanged(ChangeLevel changeLevel); + + /** + * This may be queried only ONCE (!!!) at the registration of the listener. + * + * NOTE: this allows us to only create engine level change providers if there is someone who needs it. + * + * @return the change level where you want notifications + */ + ChangeLevel getLevel(); + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseGeneratedEMFPQuery.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseGeneratedEMFPQuery.java new file mode 100644 index 00000000..c1fb0622 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseGeneratedEMFPQuery.java @@ -0,0 +1,110 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.api.impl; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EClassifier; +import org.eclipse.emf.ecore.EEnum; +import org.eclipse.emf.ecore.EEnumLiteral; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.EStructuralFeature; +import tools.refinery.viatra.runtime.exception.ViatraQueryException; +import tools.refinery.viatra.runtime.matchers.psystem.queries.BasePQuery; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PVisibility; +import tools.refinery.viatra.runtime.matchers.psystem.queries.QueryInitializationException; + +/** + * Common superclass for EMF-based generated PQueries. + * @author Bergmann Gabor + * + */ +public abstract class BaseGeneratedEMFPQuery extends BasePQuery { + + public BaseGeneratedEMFPQuery() { + this(PVisibility.PUBLIC); + } + + /** + * @since 2.0 + */ + public BaseGeneratedEMFPQuery(PVisibility visibility) { + super(visibility); + } + + protected QueryInitializationException processDependencyException(ViatraQueryException ex) { + if (ex.getCause() instanceof QueryInitializationException) + return (QueryInitializationException) ex.getCause(); + return new QueryInitializationException( + "Failed to initialize external dependencies of query specification - see 'caused by' for details.", + null, "Problem with query dependencies.", this, ex); + } + + protected EClassifier getClassifierLiteral(String packageUri, String classifierName) { + EPackage ePackage = EPackage.Registry.INSTANCE.getEPackage(packageUri); + if (ePackage == null) + throw new QueryInitializationException( + "Query refers to EPackage {1} not found in EPackage Registry.", + new String[]{packageUri}, + "Query refers to missing EPackage.", this); + EClassifier literal = ePackage.getEClassifier(classifierName); + if (literal == null) + throw new QueryInitializationException( + "Query refers to classifier {1} not found in EPackage {2}.", + new String[]{classifierName, packageUri}, + "Query refers to missing type in EPackage.", this); + return literal; + } + + /** + * For parameter type retrieval only. + * + *

    If parameter type declaration is erroneous, we still get a working parameter list (without the type declaration); + * the exception will be thrown again later when the body is processed. + */ + protected EClassifier getClassifierLiteralSafe(String packageURI, String classifierName) { + try { + return getClassifierLiteral(packageURI, classifierName); + } catch (QueryInitializationException e) { + return null; + } + } + + protected EStructuralFeature getFeatureLiteral(String packageUri, String className, String featureName) { + EClassifier container = getClassifierLiteral(packageUri, className); + if (! (container instanceof EClass)) + throw new QueryInitializationException( + "Query refers to EClass {1} in EPackage {2} which turned out not be an EClass.", + new String[]{className, packageUri}, + "Query refers to missing EClass.", this); + EStructuralFeature feature = ((EClass)container).getEStructuralFeature(featureName); + if (feature == null) + throw new QueryInitializationException( + "Query refers to feature {1} not found in EClass {2}.", + new String[]{featureName, className}, + "Query refers to missing feature.", this); + return feature; + } + + protected EEnumLiteral getEnumLiteral(String packageUri, String enumName, String literalName) { + EClassifier enumContainer = getClassifierLiteral(packageUri, enumName); + if (! (enumContainer instanceof EEnum)) + throw new QueryInitializationException( + "Query refers to EEnum {1} in EPackage {2} which turned out not be an EEnum.", + new String[]{enumName, packageUri}, + "Query refers to missing enumeration type.", this); + EEnumLiteral literal = ((EEnum)enumContainer).getEEnumLiteral(literalName); + if (literal == null) + throw new QueryInitializationException( + "Query refers to enumeration literal {1} not found in EEnum {2}.", + new String[]{literalName, enumName}, + "Query refers to missing enumeration literal.", this); + return literal; + } + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseGeneratedEMFQuerySpecification.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseGeneratedEMFQuerySpecification.java new file mode 100644 index 00000000..9956983d --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseGeneratedEMFQuerySpecification.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.api.impl; + +import tools.refinery.viatra.runtime.api.IPatternMatch; +import tools.refinery.viatra.runtime.api.ViatraQueryMatcher; +import tools.refinery.viatra.runtime.api.scope.QueryScope; +import tools.refinery.viatra.runtime.emf.EMFScope; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; + +/** + * Provides common functionality of pattern-specific generated query specifications over the EMF scope. + * + * @author Bergmann Gábor + * @author Mark Czotter + */ +public abstract class BaseGeneratedEMFQuerySpecification> extends + BaseQuerySpecification { + + + /** + * Instantiates query specification for the given internal query representation. + */ + public BaseGeneratedEMFQuerySpecification(PQuery wrappedPQuery) { + super(wrappedPQuery); + } + + @Override + public Class getPreferredScopeClass() { + return EMFScope.class; + } + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseGeneratedEMFQuerySpecificationWithGenericMatcher.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseGeneratedEMFQuerySpecificationWithGenericMatcher.java new file mode 100644 index 00000000..949dd112 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseGeneratedEMFQuerySpecificationWithGenericMatcher.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Zoltan Ujhelyi, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.api.impl; + +import tools.refinery.viatra.runtime.api.GenericPatternMatch; +import tools.refinery.viatra.runtime.api.GenericPatternMatcher; +import tools.refinery.viatra.runtime.api.GenericQuerySpecification; +import tools.refinery.viatra.runtime.api.ViatraQueryEngine; +import tools.refinery.viatra.runtime.api.scope.QueryScope; +import tools.refinery.viatra.runtime.emf.EMFScope; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; + +/** + * Provides common functionality of pattern-specific generated query specifications for without generated + * pattern-specific match and matcher classes, including private patterns. + * + * @since 1.7 + * + */ +public abstract class BaseGeneratedEMFQuerySpecificationWithGenericMatcher + extends GenericQuerySpecification { + + public BaseGeneratedEMFQuerySpecificationWithGenericMatcher(PQuery wrappedPQuery) { + super(wrappedPQuery); + } + + @Override + public Class getPreferredScopeClass() { + return EMFScope.class; + } + + @Override + protected GenericPatternMatcher instantiate(final ViatraQueryEngine engine) { + return defaultInstantiate(engine); + } + + @Override + public GenericPatternMatcher instantiate() { + return new GenericPatternMatcher(this); + } + + @Override + public GenericPatternMatch newEmptyMatch() { + return GenericPatternMatch.newEmptyMatch(this); + } + + @Override + public GenericPatternMatch newMatch(final Object... parameters) { + return GenericPatternMatch.newMatch(this, parameters); + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseGeneratedPatternGroup.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseGeneratedPatternGroup.java new file mode 100644 index 00000000..2e2a823b --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseGeneratedPatternGroup.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Mark Czotter, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.api.impl; + +import java.util.HashSet; +import java.util.Set; + +import tools.refinery.viatra.runtime.api.IQuerySpecification; + +/** + * @author Mark Czotter + * + */ +public abstract class BaseGeneratedPatternGroup extends BaseQueryGroup { + + @Override + public Set> getSpecifications() { + return querySpecifications; + } + + /** + * Returns {@link IQuerySpecification} objects for handling them as a group. To be filled by constructors of subclasses. + */ + protected Set> querySpecifications = new HashSet>(); +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseMatcher.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseMatcher.java new file mode 100644 index 00000000..ad79aafd --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseMatcher.java @@ -0,0 +1,350 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.api.impl; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import tools.refinery.viatra.runtime.api.IPatternMatch; +import tools.refinery.viatra.runtime.api.IQuerySpecification; +import tools.refinery.viatra.runtime.api.ViatraQueryEngine; +import tools.refinery.viatra.runtime.api.ViatraQueryMatcher; +import tools.refinery.viatra.runtime.internal.apiimpl.QueryResultWrapper; +import tools.refinery.viatra.runtime.matchers.backend.IMatcherCapability; +import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; + +/** + * Base implementation of ViatraQueryMatcher. + * + * @author Bergmann Gábor + * + * @param + */ +public abstract class BaseMatcher extends QueryResultWrapper implements ViatraQueryMatcher { + + // FIELDS AND CONSTRUCTOR + + protected ViatraQueryEngine engine; + protected IQuerySpecification> querySpecification; + private IMatcherCapability capabilities; + + /** + * @since 1.4 + */ + public BaseMatcher(IQuerySpecification> querySpecification) { + this.querySpecification = querySpecification; + this.querySpecification.getInternalQueryRepresentation().ensureInitialized(); + } + + /** + * @since 1.4 + */ + @Override + protected + void setBackend(ViatraQueryEngine engine, IQueryResultProvider resultProvider, IMatcherCapability capabilities){ + this.backend = resultProvider; + this.engine = engine; + this.capabilities = capabilities; + } + + // ARRAY-BASED INTERFACE + + /** Converts the array representation of a pattern match to an immutable Match object. */ + protected abstract Match arrayToMatch(Object[] parameters); + /** Converts the array representation of a pattern match to a mutable Match object. */ + protected abstract Match arrayToMatchMutable(Object[] parameters); + + /** Converts the Match object of a pattern match to the array representation. */ + protected Object[] matchToArray(Match partialMatch) { + return partialMatch.toArray(); + } + // TODO make me public for performance reasons + protected abstract Match tupleToMatch(Tuple t); + + private Object[] fEmptyArray; + + protected Object[] emptyArray() { + if (fEmptyArray == null) + fEmptyArray = new Object[getSpecification().getParameterNames().size()]; + return fEmptyArray; + } + + // REFLECTION + + @Override + public Integer getPositionOfParameter(String parameterName) { + return getSpecification().getPositionOfParameter(parameterName); + } + + @Override + public List getParameterNames() { + return getSpecification().getParameterNames(); + } + + // BASE IMPLEMENTATION + + @Override + public Collection getAllMatches() { + return rawStreamAllMatches(emptyArray()).collect(Collectors.toSet()); + } + + @Override + public Stream streamAllMatches() { + return rawStreamAllMatches(emptyArray()); + } + + /** + * Returns a stream of all matches of the pattern that conform to the given fixed values of some parameters. + * + * @param parameters + * array where each non-null element binds the corresponding pattern parameter to a fixed value. + * @pre size of input array must be equal to the number of parameters. + * @return matches represented as a Match object. + * @since 2.0 + */ + protected Stream rawStreamAllMatches(Object[] parameters) { + // clones the tuples into a match object to protect the Tuples from modifications outside of the ReteMatcher + return backend.getAllMatches(parameters).map(this::tupleToMatch); + } + + @Override + public Collection getAllMatches(Match partialMatch) { + return rawStreamAllMatches(partialMatch.toArray()).collect(Collectors.toSet()); + } + + @Override + public Stream streamAllMatches(Match partialMatch) { + return rawStreamAllMatches(partialMatch.toArray()); + } + + // with input binding as pattern-specific parameters: not declared in interface + + @Override + public Optional getOneArbitraryMatch() { + return rawGetOneArbitraryMatch(emptyArray()); + } + + /** + * Returns an arbitrarily chosen match of the pattern that conforms to the given fixed values of some parameters. + * Neither determinism nor randomness of selection is guaranteed. + * + * @param parameters + * array where each non-null element binds the corresponding pattern parameter to a fixed value. + * @pre size of input array must be equal to the number of parameters. + * @return a match represented as a Match object, or null if no match is found. + * @since 2.0 + */ + protected Optional rawGetOneArbitraryMatch(Object[] parameters) { + return backend.getOneArbitraryMatch(parameters).map(this::tupleToMatch); + } + + @Override + public Optional getOneArbitraryMatch(Match partialMatch) { + return rawGetOneArbitraryMatch(partialMatch.toArray()); + } + + // with input binding as pattern-specific parameters: not declared in interface + + /** + * Indicates whether the given combination of specified pattern parameters constitute a valid pattern match, under + * any possible substitution of the unspecified parameters. + * + * @param parameters + * array where each non-null element binds the corresponding pattern parameter to a fixed value. + * @return true if the input is a valid (partial) match of the pattern. + */ + protected boolean rawHasMatch(Object[] parameters) { + return backend.hasMatch(parameters); + } + + @Override + public boolean hasMatch() { + return rawHasMatch(emptyArray()); + } + + @Override + public boolean hasMatch(Match partialMatch) { + return rawHasMatch(partialMatch.toArray()); + } + + // with input binding as pattern-specific parameters: not declared in interface + + @Override + public int countMatches() { + return rawCountMatches(emptyArray()); + } + + /** + * Returns the number of all matches of the pattern that conform to the given fixed values of some parameters. + * + * @param parameters + * array where each non-null element binds the corresponding pattern parameter to a fixed value. + * @pre size of input array must be equal to the number of parameters. + * @return the number of pattern matches found. + */ + protected int rawCountMatches(Object[] parameters) { + return backend.countMatches(parameters); + } + + @Override + public int countMatches(Match partialMatch) { + return rawCountMatches(partialMatch.toArray()); + } + + // with input binding as pattern-specific parameters: not declared in interface + + /** + * Executes the given processor on each match of the pattern that conforms to the given fixed values of some + * parameters. + * + * @param parameters + * array where each non-null element binds the corresponding pattern parameter to a fixed value. + * @pre size of input array must be equal to the number of parameters. + * @param action + * the action that will process each pattern match. + * @since 2.0 + */ + protected void rawForEachMatch(Object[] parameters, Consumer processor) { + backend.getAllMatches(parameters).map(this::tupleToMatch).forEach(processor); + } + + @Override + public void forEachMatch(Consumer processor) { + rawForEachMatch(emptyArray(), processor); + } + + @Override + public void forEachMatch(Match match, Consumer processor) { + rawForEachMatch(match.toArray(), processor); + } + + // with input binding as pattern-specific parameters: not declared in interface + + @Override + public boolean forOneArbitraryMatch(Consumer processor) { + return rawForOneArbitraryMatch(emptyArray(), processor); + } + + @Override + public boolean forOneArbitraryMatch(Match partialMatch, Consumer processor) { + return rawForOneArbitraryMatch(partialMatch.toArray(), processor); + } + + /** + * Executes the given processor on an arbitrarily chosen match of the pattern that conforms to the given fixed + * values of some parameters. Neither determinism nor randomness of selection is guaranteed. + * + * @param parameters + * array where each non-null element binds the corresponding pattern parameter to a fixed value. + * @pre size of input array must be equal to the number of parameters. + * @param processor + * the action that will process the selected match. + * @return true if the pattern has at least one match with the given parameter values, false if the processor was + * not invoked + * @since 2.0 + */ + protected boolean rawForOneArbitraryMatch(Object[] parameters, Consumer processor) { + return backend.getOneArbitraryMatch(parameters).map(this::tupleToMatch).map(m -> { + processor.accept(m); + return true; + }).orElse(false); + } + + // with input binding as pattern-specific parameters: not declared in interface + + + @Override + public Match newEmptyMatch() { + return arrayToMatchMutable(new Object[getParameterNames().size()]); + } + + @Override + public Match newMatch(Object... parameters) { + return arrayToMatch(parameters); + } + + @Override + public Set getAllValues(final String parameterName) { + return rawStreamAllValues(getPositionOfParameter(parameterName), emptyArray()).collect(Collectors.toSet()); + } + + @Override + public Set getAllValues(final String parameterName, Match partialMatch) { + return rawStreamAllValues(getPositionOfParameter(parameterName), partialMatch.toArray()).collect(Collectors.toSet()); + } + + /** + * Retrieve a stream of values that occur in matches for the given parameterName, that conforms to the given fixed + * values of some parameters. + * + * @param position + * position of the parameter for which values are returned + * @param parameters + * a parameter array corresponding to a partial match of the pattern where each non-null field binds the + * corresponding pattern parameter to a fixed value. + * @return the stream of all values in the given position + * @throws IllegalArgumentException + * if length of parameters array does not equal to number of parameters + * @throws IndexOutOfBoundsException + * if position is not appropriate for the current parameter size + * @since 2.0 + */ + protected Stream rawStreamAllValues(final int position, Object[] parameters) { + Preconditions.checkElementIndex(position, getParameterNames().size()); + Preconditions.checkArgument(parameters.length == getParameterNames().size()); + return rawStreamAllMatches(parameters).map(match -> match.get(position)); + } + + /** + * Uses an existing set to accumulate all values of the parameter with the given name. Since it is a protected + * method, no error checking or input validation is performed! + * + * @param position + * position of the parameter for which values are returned + * @param parameters + * a parameter array corresponding to a partial match of the pattern where each non-null field binds the + * corresponding pattern parameter to a fixed value. + * @param accumulator + * the existing set to fill with the values + */ + @SuppressWarnings("unchecked") + protected void rawAccumulateAllValues(final int position, Object[] parameters, final Set accumulator) { + rawForEachMatch(parameters, match -> accumulator.add((T) match.get(position))); + } + + @Override + public ViatraQueryEngine getEngine() { + return engine; + } + + @Override + public IQuerySpecification> getSpecification() { + return querySpecification; + } + + @Override + public String getPatternName() { + return querySpecification.getFullyQualifiedName(); + } + + /** + * @since 1.4 + */ + public IMatcherCapability getCapabilities() { + return capabilities; + } +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BasePatternMatch.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BasePatternMatch.java new file mode 100644 index 00000000..7690daf6 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BasePatternMatch.java @@ -0,0 +1,115 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.api.impl; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EStructuralFeature; +import tools.refinery.viatra.runtime.api.IPatternMatch; + +/** + * Base implementation of IPatternMatch. + * + * @author Bergmann Gábor + * + */ +public abstract class BasePatternMatch implements IPatternMatch { + + @SafeVarargs + protected static List makeImmutableList(T... elements) { + return Collections.unmodifiableList(Arrays.asList(elements)); + } + + public static String prettyPrintValue(Object o) { + if (o == null) { + return "(null)"; + } + String name = prettyPrintFeature(o, "name"); + if (name != null) { + return name; + } + /* + * if (o instanceof EObject) { EStructuralFeature feature = ((EObject)o).eClass().getEStructuralFeature("name"); + * if (feature != null) { Object name = ((EObject)o).eGet(feature); if (name != null) return name.toString(); } + * } + */ + return o.toString(); + } + + public static String prettyPrintFeature(Object o, String featureName) { + if (o instanceof EObject) { + EStructuralFeature feature = ((EObject) o).eClass().getEStructuralFeature(featureName); + if (feature != null) { + Object value = ((EObject) o).eGet(feature); + if (value != null) { + return value.toString(); + } + } + } + return null; + } + + // TODO performance can be improved here somewhat + + @Override + public Object get(int position) { + if (position >= 0 && position < parameterNames().size()) + return get(parameterNames().get(position)); + else + return null; + } + + @Override + public boolean set(int position, Object newValue) { + if (!isMutable()) throw new UnsupportedOperationException(); + if (position >= 0 && position < parameterNames().size()) { + return set(parameterNames().get(position), newValue); + } else { + return false; + } + } + + @Override + public String toString() { + return "Match<" + patternName() + ">{" + prettyPrint() + "}"; + } + + @Override + public boolean isCompatibleWith(IPatternMatch other) { + if(other == null) { + return true; + } + // we assume that the pattern is set for this match! + if (!specification().equals(other.specification())) { + return false; + } + for (int i = 0; i < parameterNames().size(); i++) { + Object value = get(i); + Object otherValue = other.get(i); + if(value != null && otherValue != null && !value.equals(otherValue)) { + return false; + } + } + return true; + } + + @Override + public String patternName() { + return specification().getFullyQualifiedName(); + } + + @Override + public List parameterNames() { + return specification().getParameterNames(); + } +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseQueryGroup.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseQueryGroup.java new file mode 100644 index 00000000..b92727e7 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseQueryGroup.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2010-2012, Mark Czotter, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.api.impl; + +import tools.refinery.viatra.runtime.api.AdvancedViatraQueryEngine; +import tools.refinery.viatra.runtime.api.IQueryGroup; +import tools.refinery.viatra.runtime.api.ViatraQueryEngine; + +/** + * Base implementation of {@link IQueryGroup}. + * + * @author Mark Czotter + * + */ +public abstract class BaseQueryGroup implements IQueryGroup { + + @Override + public void prepare(ViatraQueryEngine engine) { + prepare(AdvancedViatraQueryEngine.from(engine)); + } + + protected void prepare(AdvancedViatraQueryEngine engine) { + engine.prepareGroup(this, null /* default options */); + } + + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseQuerySpecification.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseQuerySpecification.java new file mode 100644 index 00000000..bee4b93d --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/BaseQuerySpecification.java @@ -0,0 +1,147 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.api.impl; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import tools.refinery.viatra.runtime.api.IPatternMatch; +import tools.refinery.viatra.runtime.api.IQuerySpecification; +import tools.refinery.viatra.runtime.api.ViatraQueryEngine; +import tools.refinery.viatra.runtime.api.ViatraQueryMatcher; +import tools.refinery.viatra.runtime.exception.ViatraQueryException; +import tools.refinery.viatra.runtime.matchers.psystem.annotations.PAnnotation; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.psystem.queries.QueryInitializationException; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery.PQueryStatus; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PVisibility; + +/** + * Base implementation of IQuerySpecification. + * + * @author Gabor Bergmann + * + */ +public abstract class BaseQuerySpecification> implements + IQuerySpecification { + + /** + * @since 1.6 + */ + protected static ViatraQueryException processInitializerError(ExceptionInInitializerError err) { + Throwable cause1 = err.getCause(); + if (cause1 instanceof RuntimeException) { + Throwable cause2 = ((RuntimeException) cause1).getCause(); + if (cause2 instanceof ViatraQueryException) { + return (ViatraQueryException) cause2; + } else if (cause2 instanceof QueryInitializationException) { + return new ViatraQueryException((QueryInitializationException) cause2); + } + } + throw err; + } + protected final PQuery wrappedPQuery; + + protected abstract Matcher instantiate(ViatraQueryEngine engine); + + /** + * For backward compatibility of code generated with previous versions of viatra query, this method has a default + * implementation returning null, indicating that a matcher can only be created using the old method, which ignores + * the hints provided by the user. + * + * @since 1.4 + */ + @Override + public Matcher instantiate() { + return null; + } + + + /** + * Instantiates query specification for the given internal query representation. + */ + public BaseQuerySpecification(PQuery wrappedPQuery) { + super(); + this.wrappedPQuery = wrappedPQuery; + wrappedPQuery.publishedAs().add(this); + } + + + @Override + public PQuery getInternalQueryRepresentation() { + return wrappedPQuery; + } + + @Override + public Matcher getMatcher(ViatraQueryEngine engine) { + ensureInitializedInternal(); + if (wrappedPQuery.getStatus() == PQueryStatus.ERROR) { + String errorMessages = wrappedPQuery.getPProblems().stream() + .map(input -> (input == null) ? "" : input.getShortMessage()).collect(Collectors.joining("\n")); + throw new ViatraQueryException(String.format("Erroneous query specification: %s %n %s", getFullyQualifiedName(), errorMessages), + "Cannot initialize matchers on erroneous query specifications."); + } else if (!engine.getScope().isCompatibleWithQueryScope(this.getPreferredScopeClass())) { + throw new ViatraQueryException( + String.format( + "Scope class incompatibility: the query %s is formulated over query scopes of class %s, " + + " thus the query engine formulated over scope %s of class %s cannot evaluate it.", + this.getFullyQualifiedName(), this.getPreferredScopeClass().getCanonicalName(), + engine.getScope(), engine.getScope().getClass().getCanonicalName()), + "Incompatible scope classes of engine and query."); + } + return instantiate(engine); + } + + protected void ensureInitializedInternal() { + wrappedPQuery.ensureInitialized(); + } + + // // DELEGATIONS + + @Override + public List getAllAnnotations() { + return wrappedPQuery.getAllAnnotations(); + } + @Override + public List getAnnotationsByName(String annotationName) { + return wrappedPQuery.getAnnotationsByName(annotationName); + } + @Override + public Optional getFirstAnnotationByName(String annotationName) { + return wrappedPQuery.getFirstAnnotationByName(annotationName); + } + @Override + public String getFullyQualifiedName() { + return wrappedPQuery.getFullyQualifiedName(); + } + @Override + public List getParameterNames() { + return wrappedPQuery.getParameterNames(); + } + @Override + public List getParameters() { + return wrappedPQuery.getParameters(); + } + @Override + public Integer getPositionOfParameter(String parameterName) { + return wrappedPQuery.getPositionOfParameter(parameterName); + } + + /** + * @since 2.0 + */ + @Override + public PVisibility getVisibility() { + return wrappedPQuery.getVisibility(); + } + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/RunOnceQueryEngine.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/RunOnceQueryEngine.java new file mode 100644 index 00000000..a97dea5d --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/impl/RunOnceQueryEngine.java @@ -0,0 +1,140 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Abel Hegedus, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.api.impl; + +import java.util.Collection; + +import org.eclipse.emf.common.notify.Notifier; +import tools.refinery.viatra.runtime.api.AdvancedViatraQueryEngine; +import tools.refinery.viatra.runtime.api.IPatternMatch; +import tools.refinery.viatra.runtime.api.IQuerySpecification; +import tools.refinery.viatra.runtime.api.IRunOnceQueryEngine; +import tools.refinery.viatra.runtime.api.ViatraQueryEngine; +import tools.refinery.viatra.runtime.api.ViatraQueryMatcher; +import tools.refinery.viatra.runtime.api.ViatraQueryModelUpdateListener; +import tools.refinery.viatra.runtime.base.api.BaseIndexOptions; +import tools.refinery.viatra.runtime.emf.EMFScope; + +/** + * Run-once query engines can be used to retrieve the current match set of query specifications + * in a given scope. The engine is initialized with a {@link Notifier} as scope and a base index options + * that specifically allows traversing derived features that are not well-behaving. + * + * @author Abel Hegedus + * + */ +public class RunOnceQueryEngine implements IRunOnceQueryEngine { + + /** + * If the model changes, we know that a resampling is required. + * + * @author Abel Hegedus + * + */ + private final class RunOnceSamplingModelUpdateListener implements ViatraQueryModelUpdateListener { + @Override + public void notifyChanged(ChangeLevel changeLevel) { + // any model change may require re-sampling + reSamplingNeeded = true; + } + + @Override + public ChangeLevel getLevel() { + return ChangeLevel.MODEL; + } + } + + /** + * Override the default base index options to allow traversing and indexing derived features + * that would be problematic in incremental evaluation. + * + * @author Abel Hegedus + * + */ + private static final class RunOnceBaseIndexOptions extends BaseIndexOptions { + + public RunOnceBaseIndexOptions() { + this.traverseOnlyWellBehavingDerivedFeatures = false; + } + + } + + /** + * The scope of the engine that is used when creating one-time {@link ViatraQueryEngine}s. + */ + private Notifier notifier; + /** + * The options that are used for initializing the {@link ViatraQueryEngine}. + */ + private RunOnceBaseIndexOptions baseIndexOptions; + private AdvancedViatraQueryEngine engine; + private boolean reSamplingNeeded = false; + protected boolean samplingMode = false; + private RunOnceSamplingModelUpdateListener modelUpdateListener; + + /** + * Creates a run-once query engine on the given notifier. + */ + public RunOnceQueryEngine(Notifier notifier) { + this.notifier = notifier; + this.baseIndexOptions = new RunOnceBaseIndexOptions(); + } + + @Override + public Collection getAllMatches( + IQuerySpecification> querySpecification) { + + if(samplingMode && reSamplingNeeded && engine != null) { + // engine exists from earlier, but may need resampling if model changed + engine.getBaseIndex().resampleDerivedFeatures(); + } else { + // create new engine if it doesn't exists + //TODO correct scope handling + engine = AdvancedViatraQueryEngine.createUnmanagedEngine(new EMFScope(notifier, baseIndexOptions)); + } + ViatraQueryMatcher matcher = engine.getMatcher(querySpecification); + Collection allMatches = matcher.getAllMatches(); + if(samplingMode) { + engine.addModelUpdateListener(modelUpdateListener); + } else { + engine.dispose(); + engine = null; + } + return allMatches; + } + + @Override + public BaseIndexOptions getBaseIndexOptions() { + return baseIndexOptions; + } + + @Override + public Notifier getScope() { + return notifier; + } + + @Override + public void setAutomaticResampling(boolean automaticResampling) { + samplingMode = automaticResampling; + if(automaticResampling) { + if (modelUpdateListener == null) { + modelUpdateListener = new RunOnceSamplingModelUpdateListener(); + } + } else if(engine != null) { + engine.dispose(); + engine = null; + } + } + + @Override + public void resampleOnNextCall() { + reSamplingNeeded = true; + } + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/IBaseIndex.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/IBaseIndex.java new file mode 100644 index 00000000..1795a8ef --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/IBaseIndex.java @@ -0,0 +1,91 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.api.scope; + +import java.lang.reflect.InvocationTargetException; +import java.util.concurrent.Callable; + +/** + * Represents the index maintained on the model. + * @author Bergmann Gabor + * @since 0.9 + * + */ +public interface IBaseIndex { + // TODO lightweightObserver? + // TODO ViatraBaseIndexChangeListener? + + /** + * The given callback will be executed, and all model traversals and index registrations will be delayed until the + * execution is done. If there are any outstanding feature, class or datatype registrations, a single coalesced model + * traversal will initialize the caches and deliver the notifications. + * + * @param callable + */ + public V coalesceTraversals(Callable callable) throws InvocationTargetException; + + /** + * Adds a coarse-grained listener that will be invoked after the NavigationHelper index or the underlying model is changed. Can be used + * e.g. to check model contents. Not intended for general use. + * + *

    See {@link #removeBaseIndexChangeListener(ViatraBaseIndexChangeListener)} + * @param listener + */ + public void addBaseIndexChangeListener(ViatraBaseIndexChangeListener listener); + + /** + * Removes a registered listener. + * + *

    See {@link #addBaseIndexChangeListener(ViatraBaseIndexChangeListener)} + * + * @param listener + */ + public void removeBaseIndexChangeListener(ViatraBaseIndexChangeListener listener); + + /** + * Updates the value of indexed derived features that are not well-behaving. + */ + void resampleDerivedFeatures(); + + /** + * Adds a listener for internal errors in the index. A listener can only be added once. + * @param listener + * @returns true if the listener was not already added + * @since 0.8.0 + */ + boolean addIndexingErrorListener(IIndexingErrorListener listener); + /** + * Removes a listener for internal errors in the index + * @param listener + * @returns true if the listener was successfully removed (e.g. it did exist) + * @since 0.8.0 + */ + boolean removeIndexingErrorListener(IIndexingErrorListener listener); + + /** + * Register a lightweight observer that is notified if any edge starting at the given Object changes. + * + * @param observer the listener instance + * @param observedObject the observed instance object + * @return false if no observer can be registered for the given instance (e.g. it is a primitive), + * or observer was already registered (call has no effect) + */ + public boolean addInstanceObserver(IInstanceObserver observer, Object observedObject); + + /** + * Unregisters a lightweight observer for the given Object. + * + * @param observer the listener instance + * @param observedObject the observed instance object + * @return false if no observer can be registered for the given instance (e.g. it is a primitive), + * or no observer was registered previously (call has no effect) + */ + public boolean removeInstanceObserver(IInstanceObserver observer, Object observedObject); + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/IEngineContext.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/IEngineContext.java new file mode 100644 index 00000000..55060853 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/IEngineContext.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.api.scope; + +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; + +/** + * The context of the engine is instantiated by the scope, + * and provides information and services regarding the model the towards the engine. + * + * @author Bergmann Gabor + * + */ +public interface IEngineContext { + + /** + * Returns the base index. + * @throws ViatraQueryRuntimeException if the base index cannot be accessed + */ + IBaseIndex getBaseIndex(); + + /** + * Disposes this context object. Resources in the index may now be freed up. + * No more methods should be called after this one. + * + * @throws IllegalStateException if there are any active listeners to the underlying index + */ + void dispose(); + + /** + * Provides instance model information for pattern matching. + * + *

    Implementors note: must be reentrant. + * If called while index loading is already in progress, must return the single runtime context instance that will eventually index the model. + * When the runtime query context is invoked in such a case, incomplete indexes are tolerable, but change notifications must be correctly provided as loading commences. + * + * @return a runtime context for pattern matching + * @since 1.2 + * @throws ViatraQueryRuntimeException if the runtime context cannot be initialized + */ + public IQueryRuntimeContext getQueryRuntimeContext(); +} \ No newline at end of file diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/IIndexingErrorListener.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/IIndexingErrorListener.java new file mode 100644 index 00000000..d144bba6 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/IIndexingErrorListener.java @@ -0,0 +1,25 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.api.scope; + +import tools.refinery.viatra.runtime.base.api.NavigationHelper; + +/** + * + * This interface contains callbacks for various internal errors from the {@link NavigationHelper base index}. + * + * @author Zoltan Ujhelyi + * @since 0.9 + * + */ +public interface IIndexingErrorListener { + + void error(String description, Throwable t); + void fatal(String description, Throwable t); +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/IInstanceObserver.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/IInstanceObserver.java new file mode 100644 index 00000000..8ef29cbe --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/IInstanceObserver.java @@ -0,0 +1,21 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.api.scope; + + +/** + * Listener interface for lightweight observation of changes in edges leaving from given source instance elements. + * @author Bergmann Gabor + * @since 0.9 + * + */ +public interface IInstanceObserver { + void notifyBinaryChanged(Object sourceElement, Object edgeType); + void notifyTernaryChanged(Object sourceElement, Object edgeType); +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/QueryScope.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/QueryScope.java new file mode 100644 index 00000000..5456b9ea --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/QueryScope.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.api.scope; + +import tools.refinery.viatra.runtime.api.IQuerySpecification; +import tools.refinery.viatra.runtime.api.ViatraQueryEngine; +import tools.refinery.viatra.runtime.internal.apiimpl.EngineContextFactory; + +/** + * Defines a scope for a VIATRA Query engine, which determines the set of model elements that query evaluation operates on. + * + * @author Bergmann Gabor + * + */ +public abstract class QueryScope extends EngineContextFactory { + + /** + * Determines whether a query engine initialized on this scope can evaluate queries formulated against the given scope type. + *

    Every query scope class is compatible with a query engine initialized on a scope of the same class or a subclass. + * @param queryScopeClass the scope class returned by invoking {@link IQuerySpecification#getPreferredScopeClass()} on a query specification + * @return true if an {@link ViatraQueryEngine} initialized on this scope can consume an {@link IQuerySpecification} + */ + public boolean isCompatibleWithQueryScope(Class queryScopeClass) { + return queryScopeClass.isAssignableFrom(this.getClass()); + } + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/ViatraBaseIndexChangeListener.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/ViatraBaseIndexChangeListener.java new file mode 100644 index 00000000..b746e637 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/api/scope/ViatraBaseIndexChangeListener.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Abel Hegedus, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.api.scope; + +/** + * Listener interface for change notifications from the VIATRA Base index. + * + * @author Abel Hegedus + * @since 0.9 + * + */ +public interface ViatraBaseIndexChangeListener { + + /** + * NOTE: it is possible that this method is called only ONCE! Consider returning a constant value that is set in the constructor. + * + * @return true, if the listener should be notified only after index changes, false if notification is needed after each model change + */ + public boolean onlyOnIndexChange(); + + /** + * Called after a model change is handled by the VIATRA Base index and if indexChanged == onlyOnIndexChange(). + * + * @param indexChanged true, if the model change also affected the contents of the base index + */ + public void notifyChanged(boolean indexChanged); + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/DynamicEMFQueryRuntimeContext.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/DynamicEMFQueryRuntimeContext.java new file mode 100644 index 00000000..a6da213e --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/DynamicEMFQueryRuntimeContext.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.emf; + +import org.apache.log4j.Logger; +import tools.refinery.viatra.runtime.base.api.NavigationHelper; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; + +/** + * In dynamic EMF mode, we need to make sure that EEnum literal constants and values returned by eval() expressions + * are canonicalized in the same way as enum literals from the EMF model. + * + *

    This canonicalization is a one-way mapping, so + * {@link #unwrapElement(Object)} and {@link #unwrapTuple(Object)} remain NOPs. + * + * @author Bergmann Gabor + * + */ +public class DynamicEMFQueryRuntimeContext extends EMFQueryRuntimeContext { + + public DynamicEMFQueryRuntimeContext(NavigationHelper baseIndex, Logger logger, EMFScope emfScope) { + super(baseIndex, logger, emfScope); + } + + @Override + public Object wrapElement(Object externalElement) { + return baseIndex.toCanonicalValueRepresentation(externalElement); + } + + @Override + public Tuple wrapTuple(Tuple externalElements) { + Object[] elements = externalElements.getElements(); + for (int i=0; i< elements.length; ++i) + elements[i] = wrapElement(elements[i]); + return Tuples.flatTupleOf(elements); + } + + + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/EMFBaseIndexWrapper.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/EMFBaseIndexWrapper.java new file mode 100644 index 00000000..433c5f72 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/EMFBaseIndexWrapper.java @@ -0,0 +1,160 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.emf; + +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Callable; + +import org.eclipse.emf.common.notify.Notification; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EStructuralFeature; +import tools.refinery.viatra.runtime.api.scope.IBaseIndex; +import tools.refinery.viatra.runtime.api.scope.IIndexingErrorListener; +import tools.refinery.viatra.runtime.api.scope.IInstanceObserver; +import tools.refinery.viatra.runtime.api.scope.ViatraBaseIndexChangeListener; +import tools.refinery.viatra.runtime.base.api.EMFBaseIndexChangeListener; +import tools.refinery.viatra.runtime.base.api.IEMFIndexingErrorListener; +import tools.refinery.viatra.runtime.base.api.LightweightEObjectObserver; +import tools.refinery.viatra.runtime.base.api.NavigationHelper; + +/** + * Wraps the EMF base index into the IBaseIndex interface. + * @author Bergmann Gabor + * + */ +public class EMFBaseIndexWrapper implements IBaseIndex { + + private final NavigationHelper navigationHelper; + /** + * @return the underlying index object + */ + public NavigationHelper getNavigationHelper() { + return navigationHelper; + } + + /** + * @param navigationHelper + */ + public EMFBaseIndexWrapper(NavigationHelper navigationHelper) { + this.navigationHelper = navigationHelper; + } + + @Override + public void resampleDerivedFeatures() { + navigationHelper.resampleDerivedFeatures(); + } + + + @Override + public V coalesceTraversals(Callable callable) throws InvocationTargetException { + return navigationHelper.coalesceTraversals(callable); + } + + Map indexErrorListeners = + new HashMap(); + @Override + public boolean addIndexingErrorListener(final IIndexingErrorListener listener) { + if (indexErrorListeners.containsKey(listener)) return false; + IEMFIndexingErrorListener emfListener = new IEMFIndexingErrorListener() { + @Override + public void fatal(String description, Throwable t) { + listener.fatal(description, t); + } + @Override + public void error(String description, Throwable t) { + listener.error(description, t); + } + }; + indexErrorListeners.put(listener, emfListener); + return navigationHelper.addIndexingErrorListener(emfListener); + } + @Override + public boolean removeIndexingErrorListener(IIndexingErrorListener listener) { + if (!indexErrorListeners.containsKey(listener)) return false; + return navigationHelper.removeIndexingErrorListener(indexErrorListeners.remove(listener)); + } + + + Map indexChangeListeners = + new HashMap(); + @Override + public void addBaseIndexChangeListener(final ViatraBaseIndexChangeListener listener) { + EMFBaseIndexChangeListener emfListener = new EMFBaseIndexChangeListener() { + @Override + public boolean onlyOnIndexChange() { + return listener.onlyOnIndexChange(); + } + + @Override + public void notifyChanged(boolean indexChanged) { + listener.notifyChanged(indexChanged); + } + }; + indexChangeListeners.put(listener, emfListener); + navigationHelper.addBaseIndexChangeListener(emfListener); + } + @Override + public void removeBaseIndexChangeListener(ViatraBaseIndexChangeListener listener) { + final EMFBaseIndexChangeListener cListener = indexChangeListeners.remove(listener); + if (cListener != null) + navigationHelper.removeBaseIndexChangeListener(cListener); + } + + Map instanceObservers = + new HashMap(); + @Override + public boolean addInstanceObserver(final IInstanceObserver observer, + Object observedObject) { + if (observedObject instanceof EObject) { + EObjectObserver emfObserver = instanceObservers.computeIfAbsent(observer, EObjectObserver::new); + boolean success = + navigationHelper.addLightweightEObjectObserver(emfObserver, (EObject) observedObject); + if (success) emfObserver.usageCount++; + return success; + } else return false; + } + @Override + public boolean removeInstanceObserver(IInstanceObserver observer, + Object observedObject) { + if (observedObject instanceof EObject) { + EObjectObserver emfObserver = instanceObservers.get(observer); + if (emfObserver == null) + return false; + boolean success = + navigationHelper.removeLightweightEObjectObserver(emfObserver, (EObject)observedObject); + if (success) + if (0 == --emfObserver.usageCount) + instanceObservers.remove(observer); + return success; + } else return false; + } + private static class EObjectObserver implements LightweightEObjectObserver { + /** + * + */ + private final IInstanceObserver observer; + int usageCount = 0; + + /** + * @param observer + */ + private EObjectObserver(IInstanceObserver observer) { + this.observer = observer; + } + + @Override + public void notifyFeatureChanged(EObject host, + EStructuralFeature feature, Notification notification) { + observer.notifyBinaryChanged(host, feature); + } + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/EMFEngineContext.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/EMFEngineContext.java new file mode 100644 index 00000000..5fe9e23a --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/EMFEngineContext.java @@ -0,0 +1,110 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Denes Harmath, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.emf; + +import org.apache.log4j.Logger; +import org.eclipse.emf.common.notify.Notifier; +import tools.refinery.viatra.runtime.api.ViatraQueryEngine; +import tools.refinery.viatra.runtime.api.scope.IBaseIndex; +import tools.refinery.viatra.runtime.api.scope.IEngineContext; +import tools.refinery.viatra.runtime.api.scope.IIndexingErrorListener; +import tools.refinery.viatra.runtime.base.api.ViatraBaseFactory; +import tools.refinery.viatra.runtime.base.api.NavigationHelper; +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; + +/** + * Implements an engine context on EMF models. + * @author Bergmann Gabor + * + */ +class EMFEngineContext implements IEngineContext { + + private final EMFScope emfScope; + ViatraQueryEngine engine; + Logger logger; + NavigationHelper navHelper; + IBaseIndex baseIndex; + IIndexingErrorListener taintListener; + private EMFQueryRuntimeContext runtimeContext; + + public EMFEngineContext(EMFScope emfScope, ViatraQueryEngine engine, IIndexingErrorListener taintListener, Logger logger) { + this.emfScope = emfScope; + this.engine = engine; + this.logger = logger; + this.taintListener = taintListener; + } + + /** + * @throws ViatraQueryRuntimeException thrown if the navigation helper cannot be initialized + */ + public NavigationHelper getNavHelper() { + return getNavHelper(true); + } + + private NavigationHelper getNavHelper(boolean ensureInitialized) { + if (navHelper == null) { + // sync to avoid crazy compiler reordering which would matter if derived features use VIATRA and call this + // reentrantly + synchronized (this) { + navHelper = ViatraBaseFactory.getInstance().createNavigationHelper(null, this.emfScope.getOptions(), + logger); + getBaseIndex().addIndexingErrorListener(taintListener); + } + + if (ensureInitialized) { + ensureIndexLoaded(); + } + + } + return navHelper; + } + + private void ensureIndexLoaded() { + for (Notifier scopeRoot : this.emfScope.getScopeRoots()) { + navHelper.addRoot(scopeRoot); + } + } + + @Override + public IQueryRuntimeContext getQueryRuntimeContext() { + NavigationHelper nh = getNavHelper(false); + if (runtimeContext == null) { + runtimeContext = + emfScope.getOptions().isDynamicEMFMode() ? + new DynamicEMFQueryRuntimeContext(nh, logger, emfScope) : + new EMFQueryRuntimeContext(nh, logger, emfScope); + + ensureIndexLoaded(); + } + + return runtimeContext; + } + + @Override + public void dispose() { + if (runtimeContext != null) runtimeContext.dispose(); + if (navHelper != null) navHelper.dispose(); + + this.baseIndex = null; + this.engine = null; + this.logger = null; + this.navHelper = null; + } + + + @Override + public IBaseIndex getBaseIndex() { + if (baseIndex == null) { + final NavigationHelper navigationHelper = getNavHelper(); + baseIndex = new EMFBaseIndexWrapper(navigationHelper); + } + return baseIndex; + } +} \ No newline at end of file diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/EMFQueryMetaContext.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/EMFQueryMetaContext.java new file mode 100644 index 00000000..c316d308 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/EMFQueryMetaContext.java @@ -0,0 +1,405 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.emf; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.EAttribute; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EClassifier; +import org.eclipse.emf.ecore.EDataType; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.emf.ecore.EcorePackage; +import tools.refinery.viatra.runtime.emf.types.BaseEMFTypeKey; +import tools.refinery.viatra.runtime.emf.types.EClassTransitiveInstancesKey; +import tools.refinery.viatra.runtime.emf.types.EClassUnscopedTransitiveInstancesKey; +import tools.refinery.viatra.runtime.emf.types.EDataTypeInSlotsKey; +import tools.refinery.viatra.runtime.emf.types.EStructuralFeatureInstancesKey; +import tools.refinery.viatra.runtime.matchers.context.AbstractQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.InputKeyImplication; +import tools.refinery.viatra.runtime.matchers.context.common.JavaTransitiveInstancesKey; + +/** + * The meta context information for EMF scopes. + * + *

    The runtime context may specialize answers with a given scope. + * In a static context, a conservative default version ({@link #DEFAULT}) can be used instead. + * + *

    TODO generics? + * @author Bergmann Gabor + * + */ +public final class EMFQueryMetaContext extends AbstractQueryMetaContext { + + /** + * Default static instance that only makes conservative assumptions that are valid for any {@link EMFScope} (but not if objects are used). + * @since 1.6 + */ + public static final EMFQueryMetaContext DEFAULT = new EMFQueryMetaContext(false, true, UnscopedTypeSupport.EMIT_ALWAYS); + + /** + * Default static instance that only makes conservative assumptions that are valid for any scope, even with surrogate objects. + * Unscoped types are used for inference, but not emitted as replacement candidates, as they cannot be checked at runtime. + * @since 2.1 + */ + public static final EMFQueryMetaContext DEFAULT_SURROGATE = new EMFQueryMetaContext(false, true, UnscopedTypeSupport.EMIT_EXCEPT_AS_WEAKENED_REPLACEMENT); + + + private static final EClass EOBJECT_CLASS = + EcorePackage.eINSTANCE.getEObject(); + private static final EClassTransitiveInstancesKey EOBJECT_SCOPED_KEY = + new EClassTransitiveInstancesKey(EOBJECT_CLASS); + private static final EClassUnscopedTransitiveInstancesKey EOBJECT_UNSCOPED_KEY = + new EClassUnscopedTransitiveInstancesKey(EOBJECT_CLASS); + + private boolean assumeNonDangling; + private boolean subResourceScopeSplit; + private UnscopedTypeSupport emitUnscopedEClassTypes; + + private enum UnscopedTypeSupport { + EMIT_ALWAYS, + EMIT_EXCEPT_AS_WEAKENED_REPLACEMENT, + EMIT_NEVER + } + + + /** + * Instantiates a specialized meta information that is aware of scope-specific details. + * Note that this API is not stable and thus non-public. + * + * @param assumeNonDangling assumes that all cross-references are non-dangling (do not lead out of scope), no matter what + * @param subResourceScopeSplit the scope granularity may be finer than resource-level, i.e. proxy-non-resolving references can lead out of scope + * @param emitUnscopedEClassTypes if requested, the metacontext will suppress unscoped input keys; this is recommended if surrogates are used instead of EObjects + */ + EMFQueryMetaContext(boolean assumeNonDangling, boolean subResourceScopeSplit, UnscopedTypeSupport emitUnscopedEClassTypes) { + this.assumeNonDangling = assumeNonDangling; + this.subResourceScopeSplit = subResourceScopeSplit; + this.emitUnscopedEClassTypes = emitUnscopedEClassTypes; + } + + /** + * Instantiates a specialized meta information that is aware of scope-specific details. + * @since 2.1 + */ + public EMFQueryMetaContext(EMFScope scope) { + this(scope.getOptions().isDanglingFreeAssumption(), + scope.getScopeRoots().size()==1 && scope.getScopeRoots().iterator().next() instanceof EObject, + UnscopedTypeSupport.EMIT_ALWAYS); + } + + + @Override + public boolean isEnumerable(IInputKey key) { + ensureValidKey(key); + return key.isEnumerable(); +// if (key instanceof JavaTransitiveInstancesKey) +// return false; +// else +// return true; + } + + @Override + public boolean canLeadOutOfScope(IInputKey key) { + ensureValidKey(key); + if (key instanceof EStructuralFeatureInstancesKey) { + EStructuralFeature feature = ((EStructuralFeatureInstancesKey) key).getEmfKey(); + if (feature instanceof EReference){ + return canLeadOutOfScope((EReference) feature); + } + } + return false; + } + + /** + * Tells whether the given reference may lead out of scope. + * @since 2.1 + */ + public boolean canLeadOutOfScope(EReference reference) { + // Is it possible that this edge is dangling, i.e. its target lies outside of the scope? + // Unless non-dangling is globally assumed, + // proxy-resolving references (incl. containment) might point to proxies and are thus considered unsafe. + // Additionally, if the scope is sub-resource (containment subtree of object), + // all non-containment edges are also unsafe. + // Note that in case of cross-resource containment, + // the scope includes the contained object even if it is in a foreign resource. + return (!assumeNonDangling) + && (reference.isResolveProxies() || (subResourceScopeSplit && !reference.isContainment())); + } + + @Override + public boolean isStateless(IInputKey key) { + ensureValidKey(key); + return key instanceof JavaTransitiveInstancesKey || key instanceof EClassUnscopedTransitiveInstancesKey; + } + + @Override + public Map, Set> getFunctionalDependencies(IInputKey key) { + ensureValidKey(key); + if (key instanceof EStructuralFeatureInstancesKey) { + EStructuralFeature feature = ((EStructuralFeatureInstancesKey) key).getEmfKey(); + final Map, Set> result = + new HashMap, Set>(); + if (isFeatureMultiplicityToOne(feature)) + result.put(Collections.singleton(0), Collections.singleton(1)); + if (isFeatureMultiplicityOneTo(feature)) + result.put(Collections.singleton(1), Collections.singleton(0)); + return result; + } else { + return Collections.emptyMap(); + } + } + + /** + * @since 2.1 + */ + public EClassTransitiveInstancesKey getSourceTypeKey(EStructuralFeatureInstancesKey key) { + return new EClassTransitiveInstancesKey(key.getEmfKey().getEContainingClass()); + } + /** + * @since 2.1 + */ + public IInputKey getTargetTypeKey(EStructuralFeatureInstancesKey key) { + EStructuralFeature feature = key.getEmfKey(); + if (feature instanceof EAttribute) { + return new EDataTypeInSlotsKey(((EAttribute) feature).getEAttributeType()); + } else if (feature instanceof EReference) { + EClass eReferenceType = ((EReference) feature).getEReferenceType(); + if (canLeadOutOfScope(key)) { + return new EClassUnscopedTransitiveInstancesKey(eReferenceType); + } else { + return new EClassTransitiveInstancesKey(eReferenceType); + } + } else throw new IllegalArgumentException(); + } + + @Override + public Collection getImplications(IInputKey implyingKey) { + ensureValidKey(implyingKey); + Collection result = new HashSet(); + + if (implyingKey instanceof EClassTransitiveInstancesKey) { + EClass eClass = ((EClassTransitiveInstancesKey) implyingKey).getEmfKey(); + + // direct eSuperClasses + EList directSuperTypes = eClass.getESuperTypes(); + if (!directSuperTypes.isEmpty()) { + for (EClass superType : directSuperTypes) { + final EClassTransitiveInstancesKey implied = new EClassTransitiveInstancesKey(superType); + result.add(new InputKeyImplication(implyingKey, implied, Arrays.asList(0))); + } + } else { + if (!EOBJECT_SCOPED_KEY.equals(implyingKey)) { + result.add(new InputKeyImplication(implyingKey, EOBJECT_SCOPED_KEY, Arrays.asList(0))); + } + } + // implies unscoped + if (UnscopedTypeSupport.EMIT_NEVER != emitUnscopedEClassTypes) + result.add(new InputKeyImplication(implyingKey, + new EClassUnscopedTransitiveInstancesKey(eClass), + Arrays.asList(0))); + } else if (implyingKey instanceof EClassUnscopedTransitiveInstancesKey) { + EClass eClass = ((EClassUnscopedTransitiveInstancesKey) implyingKey).getEmfKey(); + + // direct eSuperClasses + EList directSuperTypes = eClass.getESuperTypes(); + if (!directSuperTypes.isEmpty()) { + for (EClass superType : directSuperTypes) { + final EClassUnscopedTransitiveInstancesKey implied = new EClassUnscopedTransitiveInstancesKey( + superType); + result.add(new InputKeyImplication(implyingKey, implied, Arrays.asList(0))); + } + } else { + if (!EOBJECT_UNSCOPED_KEY.equals(implyingKey)) { + result.add(new InputKeyImplication(implyingKey, EOBJECT_UNSCOPED_KEY, Arrays.asList(0))); + } + } + + } else if (implyingKey instanceof JavaTransitiveInstancesKey) { + Class instanceClass = ((JavaTransitiveInstancesKey) implyingKey).getInstanceClass(); + if (instanceClass != null) { // resolution successful + // direct Java superClass + Class superclass = instanceClass.getSuperclass(); + if (superclass != null) { + JavaTransitiveInstancesKey impliedSuper = new JavaTransitiveInstancesKey(superclass); + result.add(new InputKeyImplication(implyingKey, impliedSuper, Arrays.asList(0))); + } + + // direct Java superInterfaces + for (Class superInterface : instanceClass.getInterfaces()) { + if (superInterface != null) { + JavaTransitiveInstancesKey impliedInterface = new JavaTransitiveInstancesKey(superInterface); + result.add(new InputKeyImplication(implyingKey, impliedInterface, Arrays.asList(0))); + } + } + } + + } else if (implyingKey instanceof EStructuralFeatureInstancesKey) { + EStructuralFeature feature = ((EStructuralFeatureInstancesKey) implyingKey).getEmfKey(); + + // source and target type + final EClass sourceType = featureSourceType(feature); + final EClassTransitiveInstancesKey impliedSource = new EClassTransitiveInstancesKey(sourceType); + final EClassifier targetType = featureTargetType(feature); + final IInputKey impliedTarget; + if (feature instanceof EReference) { + EReference reference = (EReference) feature; + + if (!canLeadOutOfScope(reference)) { + impliedTarget = new EClassTransitiveInstancesKey((EClass) targetType); + } else { + impliedTarget = (UnscopedTypeSupport.EMIT_NEVER != emitUnscopedEClassTypes) ? + new EClassUnscopedTransitiveInstancesKey((EClass) targetType) + : null; + } + } else { // EDatatype + impliedTarget = new EDataTypeInSlotsKey((EDataType) targetType); + } + + result.add(new InputKeyImplication(implyingKey, impliedSource, Arrays.asList(0))); + if (impliedTarget != null) + result.add(new InputKeyImplication(implyingKey, impliedTarget, Arrays.asList(1))); + + // opposite + EReference opposite = featureOpposite(feature); + if (opposite != null && !canLeadOutOfScope((EReference) feature)) { + EStructuralFeatureInstancesKey impliedOpposite = new EStructuralFeatureInstancesKey(opposite); + result.add(new InputKeyImplication(implyingKey, impliedOpposite, Arrays.asList(1, 0))); + } + + // containment + // TODO + } else if (implyingKey instanceof EDataTypeInSlotsKey) { + EDataType dataType = ((EDataTypeInSlotsKey) implyingKey).getEmfKey(); + + // instance class of datatype + // TODO this can have a generation gap! (could be some dynamic EMF impl or whatever) + Class instanceClass = dataType.getInstanceClass(); + if (instanceClass != null) { + JavaTransitiveInstancesKey implied = new JavaTransitiveInstancesKey(instanceClass); + result.add(new InputKeyImplication(implyingKey, implied, Arrays.asList(0))); + } + } else { + illegalInputKey(implyingKey); + } + + return result; + } + + @Override + public Map> getConditionalImplications(IInputKey implyingKey) { + ensureValidKey(implyingKey); + if (implyingKey instanceof EClassUnscopedTransitiveInstancesKey) { + EClass emfKey = ((EClassUnscopedTransitiveInstancesKey) implyingKey).getEmfKey(); + + Map> result = new HashMap<>(); + result.put( + new InputKeyImplication(implyingKey, EOBJECT_SCOPED_KEY, Arrays.asList(0)), + new HashSet<>(Arrays.asList(new InputKeyImplication(implyingKey, new EClassTransitiveInstancesKey(emfKey), Arrays.asList(0)))) + ); + return result; + } else return super.getConditionalImplications(implyingKey); + } + + @Override + public Collection getWeakenedAlternatives(IInputKey implyingKey) { + ensureValidKey(implyingKey); + if (UnscopedTypeSupport.EMIT_ALWAYS == emitUnscopedEClassTypes && implyingKey instanceof EClassTransitiveInstancesKey) { + EClass emfKey = ((EClassTransitiveInstancesKey) implyingKey).getEmfKey(); + + Collection result = new HashSet(); + result.add( + // in some cases, filtering by the the unscoped key may be sufficient + new InputKeyImplication(implyingKey, new EClassUnscopedTransitiveInstancesKey(emfKey), Arrays.asList(0)) + ); + return result; + } else return super.getWeakenedAlternatives(implyingKey); + } + + public void ensureValidKey(IInputKey key) { + if (! (key instanceof BaseEMFTypeKey) && ! (key instanceof JavaTransitiveInstancesKey)) + illegalInputKey(key); + } + + public void illegalInputKey(IInputKey key) { + throw new IllegalArgumentException("The input key " + key + " is not a valid EMF input key."); + } + + public boolean isFeatureMultiplicityToOne(EStructuralFeature feature) { + return !feature.isMany(); + } + + public boolean isFeatureMultiplicityOneTo(EStructuralFeature typeObject) { + if (typeObject instanceof EReference) { + final EReference feature = (EReference)typeObject; + final EReference eOpposite = feature.getEOpposite(); + return feature.isContainment() || (eOpposite != null && !eOpposite.isMany()); + } else return false; + } + + public EClass featureSourceType(EStructuralFeature feature) { + return feature.getEContainingClass(); + } + public EClassifier featureTargetType(EStructuralFeature typeObject) { + if (typeObject instanceof EAttribute) { + EAttribute attribute = (EAttribute) typeObject; + return attribute.getEAttributeType(); + } else if (typeObject instanceof EReference) { + EReference reference = (EReference) typeObject; + return reference.getEReferenceType(); + } else + throw new IllegalArgumentException("typeObject has invalid type " + typeObject.getClass().getName()); + } + public EReference featureOpposite(EStructuralFeature typeObject) { + if (typeObject instanceof EReference) { + EReference reference = (EReference) typeObject; + return reference.getEOpposite(); + } else return null; + } + + @Override + public Comparator getSuggestedEliminationOrdering() { + return SUGGESTED_ELIMINATION_ORDERING; + } + + private static final Comparator SUGGESTED_ELIMINATION_ORDERING = new Comparator() { + @Override + public int compare(IInputKey o1, IInputKey o2) { + if (o1 instanceof EClassTransitiveInstancesKey && o2 instanceof EClassTransitiveInstancesKey) { + // common EClass types with many instances should be eliminated before rare types + return getRarity((EClassTransitiveInstancesKey)o1) - getRarity((EClassTransitiveInstancesKey)o2); + } else { + return getKeyTypeEliminationSequence(o1) - getKeyTypeEliminationSequence(o2); + } + } + + // The more supertypes there are, the more specialized the type + // the more specialized the type, the rarer instances are expected to be found + private int getRarity(EClassTransitiveInstancesKey key) { + return key.getEmfKey().getEAllSuperTypes().size() + (EOBJECT_SCOPED_KEY.equals(key) ? 0 : 1); + } + + // Scoped EClass transitive instance keys are attempted to be eliminated before all else + // so that e.g. their unscoped version can eliminate them is variable is known to be scoped + private int getKeyTypeEliminationSequence(IInputKey o1) { + return (o1 instanceof EClassTransitiveInstancesKey) ? -1 : 0; + } + }; + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/EMFQueryRuntimeContext.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/EMFQueryRuntimeContext.java new file mode 100644 index 00000000..7809cd24 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/EMFQueryRuntimeContext.java @@ -0,0 +1,839 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.emf; + +import java.lang.reflect.InvocationTargetException; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.apache.log4j.Logger; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EDataType; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EStructuralFeature; +import tools.refinery.viatra.runtime.base.api.DataTypeListener; +import tools.refinery.viatra.runtime.base.api.FeatureListener; +import tools.refinery.viatra.runtime.base.api.IndexingLevel; +import tools.refinery.viatra.runtime.base.api.InstanceListener; +import tools.refinery.viatra.runtime.base.api.NavigationHelper; +import tools.refinery.viatra.runtime.emf.types.EClassTransitiveInstancesKey; +import tools.refinery.viatra.runtime.emf.types.EClassUnscopedTransitiveInstancesKey; +import tools.refinery.viatra.runtime.emf.types.EDataTypeInSlotsKey; +import tools.refinery.viatra.runtime.emf.types.EStructuralFeatureInstancesKey; +import tools.refinery.viatra.runtime.matchers.context.AbstractQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.IQueryMetaContext; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContextListener; +import tools.refinery.viatra.runtime.matchers.context.IndexingService; +import tools.refinery.viatra.runtime.matchers.context.common.JavaTransitiveInstancesKey; +import tools.refinery.viatra.runtime.matchers.tuple.ITuple; +import tools.refinery.viatra.runtime.matchers.tuple.Tuple; +import tools.refinery.viatra.runtime.matchers.tuple.TupleMask; +import tools.refinery.viatra.runtime.matchers.tuple.Tuples; +import tools.refinery.viatra.runtime.matchers.util.Accuracy; + +/** + * The EMF-based runtime query context, backed by an IQBase NavigationHelper. + * + * @author Bergmann Gabor + * + *

    TODO: {@link #ensureIndexed(EClass)} may be inefficient if supertype already cached. + * @since 1.4 + */ +public class EMFQueryRuntimeContext extends AbstractQueryRuntimeContext { + protected final NavigationHelper baseIndex; + //private BaseIndexListener listener; + + protected final Map> indexedClasses = new HashMap<>(); + protected final Map> indexedDataTypes = new HashMap<>(); + protected final Map> indexedFeatures = new HashMap<>(); + + protected final EMFQueryMetaContext metaContext; + + protected Logger logger; + + private EMFScope emfScope; + + public EMFQueryRuntimeContext(NavigationHelper baseIndex, Logger logger, EMFScope emfScope) { + this.baseIndex = baseIndex; + this.logger = logger; + this.metaContext = new EMFQueryMetaContext(emfScope); + this.emfScope = emfScope; + } + + public EMFScope getEmfScope() { + return emfScope; + } + + /** + * Utility method to add an indexing service to a given key. Returns true if the requested service was + * not present before this call. + * @param map + * @param key + * @param service + * @return + */ + private static boolean addIndexingService(Map> map, K key, IndexingService service){ + EnumSet current = map.get(key); + if (current == null){ + current = EnumSet.of(service); + map.put(key, current); + return true; + }else{ + return current.add(service); + } + } + + public void dispose() { + //baseIndex.removeFeatureListener(indexedFeatures, listener); + indexedFeatures.clear(); + //baseIndex.removeInstanceListener(indexedClasses, listener); + indexedClasses.clear(); + //baseIndex.removeDataTypeListener(indexedDataTypes, listener); + indexedDataTypes.clear(); + + // No need to remove listeners, as NavHelper will be disposed imminently. + } + + @Override + public V coalesceTraversals(Callable callable) throws InvocationTargetException { + return baseIndex.coalesceTraversals(callable); + } + + @Override + public boolean isCoalescing() { + return baseIndex.isCoalescing(); + } + + @Override + public IQueryMetaContext getMetaContext() { + return metaContext; + } + + @Override + public void ensureIndexed(IInputKey key, IndexingService service) { + ensureEnumerableKey(key); + if (key instanceof EClassTransitiveInstancesKey) { + EClass eClass = ((EClassTransitiveInstancesKey) key).getEmfKey(); + ensureIndexed(eClass, service); + } else if (key instanceof EDataTypeInSlotsKey) { + EDataType dataType = ((EDataTypeInSlotsKey) key).getEmfKey(); + ensureIndexed(dataType, service); + } else if (key instanceof EStructuralFeatureInstancesKey) { + EStructuralFeature feature = ((EStructuralFeatureInstancesKey) key).getEmfKey(); + ensureIndexed(feature, service); + } else { + illegalInputKey(key); + } + } + + /** + * Retrieve the current registered indexing services for the given key. May not return null, + * returns an empty set if no indexing is registered. + * + * @since 1.4 + */ + protected EnumSet getCurrentIndexingServiceFor(IInputKey key){ + ensureEnumerableKey(key); + if (key instanceof EClassTransitiveInstancesKey) { + EClass eClass = ((EClassTransitiveInstancesKey) key).getEmfKey(); + EnumSet is = indexedClasses.get(eClass); + return is == null ? EnumSet.noneOf(IndexingService.class) : is; + } else if (key instanceof EDataTypeInSlotsKey) { + EDataType dataType = ((EDataTypeInSlotsKey) key).getEmfKey(); + EnumSet is = indexedDataTypes.get(dataType); + return is == null ? EnumSet.noneOf(IndexingService.class) : is; + } else if (key instanceof EStructuralFeatureInstancesKey) { + EStructuralFeature feature = ((EStructuralFeatureInstancesKey) key).getEmfKey(); + EnumSet is = indexedFeatures.get(feature); + return is == null ? EnumSet.noneOf(IndexingService.class) : is; + } else { + illegalInputKey(key); + return EnumSet.noneOf(IndexingService.class); + } + } + + @Override + public boolean isIndexed(IInputKey key, IndexingService service) { + return getCurrentIndexingServiceFor(key).contains(service); + } + + @Override + public boolean containsTuple(IInputKey key, ITuple seed) { + ensureValidKey(key); + if (key instanceof JavaTransitiveInstancesKey) { + Class instanceClass = forceGetWrapperInstanceClass((JavaTransitiveInstancesKey) key); + return instanceClass != null && instanceClass.isInstance(seed.get(0)); + } else if (key instanceof EClassUnscopedTransitiveInstancesKey) { + EClass emfKey = ((EClassUnscopedTransitiveInstancesKey) key).getEmfKey(); + Object candidateInstance = seed.get(0); + return candidateInstance instanceof EObject + && baseIndex.isInstanceOfUnscoped((EObject) candidateInstance, emfKey); + } else { + ensureIndexed(key, IndexingService.INSTANCES); + if (key instanceof EClassTransitiveInstancesKey) { + EClass eClass = ((EClassTransitiveInstancesKey) key).getEmfKey(); + // instance check not enough to satisfy scoping, must lookup from index + Object candidateInstance = seed.get(0); + return candidateInstance instanceof EObject + && baseIndex.isInstanceOfScoped((EObject) candidateInstance, eClass); + } else if (key instanceof EDataTypeInSlotsKey) { + EDataType dataType = ((EDataTypeInSlotsKey) key).getEmfKey(); + return baseIndex.isInstanceOfDatatype(seed.get(0), dataType); + } else if (key instanceof EStructuralFeatureInstancesKey) { + EStructuralFeature feature = ((EStructuralFeatureInstancesKey) key).getEmfKey(); + Object sourceCandidate = seed.get(0); + return sourceCandidate instanceof EObject + && baseIndex.isFeatureInstance((EObject) sourceCandidate, seed.get(1), feature); + } else { + illegalInputKey(key); + return false; + } + } + } + + private Class forceGetWrapperInstanceClass(JavaTransitiveInstancesKey key) { + Class instanceClass; + try { + instanceClass = key.forceGetWrapperInstanceClass(); + } catch (ClassNotFoundException e) { + logger.error("Could not load instance class for type constraint " + key.getWrappedKey(), e); + instanceClass = null; + } + return instanceClass; + } + + @Override + public Iterable enumerateTuples(IInputKey key, TupleMask seedMask, ITuple seed) { + ensureIndexed(key, IndexingService.INSTANCES); + final Collection result = new HashSet(); + + if (key instanceof EClassTransitiveInstancesKey) { + EClass eClass = ((EClassTransitiveInstancesKey) key).getEmfKey(); + + if (seedMask.indices.length == 0) { // unseeded + return baseIndex.getAllInstances(eClass).stream().map(wrapUnary).collect(Collectors.toSet()); + } else { // fully seeded + Object seedInstance = seedMask.getValue(seed, 0); + if (containsTuple(key, seed)) + result.add(Tuples.staticArityFlatTupleOf(seedInstance)); + } + } else if (key instanceof EDataTypeInSlotsKey) { + EDataType dataType = ((EDataTypeInSlotsKey) key).getEmfKey(); + + if (seedMask.indices.length == 0) { // unseeded + return baseIndex.getDataTypeInstances(dataType).stream().map(wrapUnary).collect(Collectors.toSet()); + } else { // fully seeded + Object seedInstance = seedMask.getValue(seed, 0); + if (containsTuple(key, seed)) + result.add(Tuples.staticArityFlatTupleOf(seedInstance)); + } + } else if (key instanceof EStructuralFeatureInstancesKey) { + EStructuralFeature feature = ((EStructuralFeatureInstancesKey) key).getEmfKey(); + + boolean isSourceBound = false; + int sourceIndex = -1; + boolean isTargetBound = false; + int targetIndex = -1; + for (int i = 0; i < seedMask.getSize(); i++) { + int index = seedMask.indices[i]; + if (index == 0) { + isSourceBound = true; + sourceIndex = i; + } else if (index == 1) { + isTargetBound = true; + targetIndex = i; + } + } + + if (!isSourceBound && isTargetBound) { + final Object seedTarget = seed.get(targetIndex); + final Set results = baseIndex.findByFeatureValue(seedTarget, feature); + return results.stream().map(obj -> Tuples.staticArityFlatTupleOf(obj, seedTarget)).collect(Collectors.toSet()); + } else if (isSourceBound && isTargetBound) { // fully seeded + final Object seedSource = seed.get(sourceIndex); + final Object seedTarget = seed.get(targetIndex); + if (containsTuple(key, seed)) + result.add(Tuples.staticArityFlatTupleOf(seedSource, seedTarget)); + } else if (!isSourceBound && !isTargetBound) { // fully unseeded + baseIndex.processAllFeatureInstances(feature, (source, target) -> result.add(Tuples.staticArityFlatTupleOf(source, target))); + } else if (isSourceBound && !isTargetBound) { + final Object seedSource = seed.get(sourceIndex); + final Set results = baseIndex.getFeatureTargets((EObject) seedSource, feature); + return results.stream().map(obj -> Tuples.staticArityFlatTupleOf(seedSource, obj)).collect(Collectors.toSet()); + } + } else { + illegalInputKey(key); + } + + + return result; + } + + private static Function wrapUnary = Tuples::staticArityFlatTupleOf; + + @Override + public Iterable enumerateValues(IInputKey key, TupleMask seedMask, ITuple seed) { + ensureIndexed(key, IndexingService.INSTANCES); + + if (key instanceof EClassTransitiveInstancesKey) { + EClass eClass = ((EClassTransitiveInstancesKey) key).getEmfKey(); + + if (seedMask.indices.length == 0) { // unseeded + return baseIndex.getAllInstances(eClass); + } else { + // must be unseeded, this is enumerateValues after all! + illegalEnumerateValues(seed.toImmutable()); + } + } else if (key instanceof EDataTypeInSlotsKey) { + EDataType dataType = ((EDataTypeInSlotsKey) key).getEmfKey(); + + if (seedMask.indices.length == 0) { // unseeded + return baseIndex.getDataTypeInstances(dataType); + } else { + // must be unseeded, this is enumerateValues after all! + illegalEnumerateValues(seed.toImmutable()); + } + } else if (key instanceof EStructuralFeatureInstancesKey) { + EStructuralFeature feature = ((EStructuralFeatureInstancesKey) key).getEmfKey(); + + boolean isSourceBound = false; + int sourceIndex = -1; + boolean isTargetBound = false; + int targetIndex = -1; + for (int i = 0; i < seedMask.getSize(); i++) { + int index = seedMask.indices[i]; + if (index == 0) { + isSourceBound = true; + sourceIndex = i; + } else if (index == 1) { + isTargetBound = true; + targetIndex = i; + } + } + + if (!isSourceBound && isTargetBound) { + Object seedTarget = seed.get(targetIndex); + return baseIndex.findByFeatureValue(seedTarget, feature); + } else if (isSourceBound && !isTargetBound) { + Object seedSource = seed.get(sourceIndex); + return baseIndex.getFeatureTargets((EObject) seedSource, feature); + } else { + // must be singly unseeded, this is enumerateValues after all! + illegalEnumerateValues(seed.toImmutable()); + } + } else { + illegalInputKey(key); + } + return null; + } + + @Override + public int countTuples(IInputKey key, TupleMask seedMask, ITuple seed) { + ensureIndexed(key, IndexingService.STATISTICS); + + if (key instanceof EClassTransitiveInstancesKey) { + EClass eClass = ((EClassTransitiveInstancesKey) key).getEmfKey(); + + if (seedMask.indices.length == 0) { // unseeded + return baseIndex.countAllInstances(eClass); + } else { // fully seeded + return (containsTuple(key, seed)) ? 1 : 0; + } + } else if (key instanceof EDataTypeInSlotsKey) { + EDataType dataType = ((EDataTypeInSlotsKey) key).getEmfKey(); + + if (seedMask.indices.length == 0) { // unseeded + return baseIndex.countDataTypeInstances(dataType); + } else { // fully seeded + return (containsTuple(key, seed)) ? 1 : 0; + } + } else if (key instanceof EStructuralFeatureInstancesKey) { + EStructuralFeature feature = ((EStructuralFeatureInstancesKey) key).getEmfKey(); + + boolean isSourceBound = false; + int sourceIndex = -1; + boolean isTargetBound = false; + int targetIndex = -1; + for (int i = 0; i < seedMask.getSize(); i++) { + int index = seedMask.indices[i]; + if (index == 0) { + isSourceBound = true; + sourceIndex = i; + } else if (index == 1) { + isTargetBound = true; + targetIndex = i; + } + } + + if (!isSourceBound && isTargetBound) { + final Object seedTarget = seed.get(targetIndex); + return baseIndex.findByFeatureValue(seedTarget, feature).size(); + } else if (isSourceBound && isTargetBound) { // fully seeded + return (containsTuple(key, seed)) ? 1 : 0; + } else if (!isSourceBound && !isTargetBound) { // fully unseeded + return baseIndex.countFeatures(feature); + } else if (isSourceBound && !isTargetBound) { + final Object seedSource = seed.get(sourceIndex); + return baseIndex.countFeatureTargets((EObject) seedSource, feature); + } + } else { + illegalInputKey(key); + } + return 0; + } + + + /** + * @since 2.1 + */ + @Override + public Optional estimateCardinality(IInputKey key, TupleMask groupMask, Accuracy requiredAccuracy) { + + if (key instanceof EClassTransitiveInstancesKey) { + EClass eClass = ((EClassTransitiveInstancesKey) key).getEmfKey(); + + if (isIndexed(key, IndexingService.STATISTICS)) { // exact answer known + if (groupMask.indices.length == 0) { // empty projection + return (0 != baseIndex.countAllInstances(eClass)) ? Optional.of(1L) : Optional.of(0L); + } else { // unprojected + return Optional.of((long)baseIndex.countAllInstances(eClass)); + } + } else return Optional.empty(); // TODO use known supertype counts as upper, subtypes as lower bounds + + } else if (key instanceof EClassUnscopedTransitiveInstancesKey) { + EClass eClass = ((EClassUnscopedTransitiveInstancesKey) key).getEmfKey(); + + // can give only lower bound based on the scoped key + if (Accuracy.BEST_LOWER_BOUND.atLeastAsPreciseAs(requiredAccuracy)) { + return estimateCardinality(new EClassTransitiveInstancesKey(eClass), groupMask, requiredAccuracy); + } else return Optional.empty(); + + } else if (key instanceof EDataTypeInSlotsKey) { + EDataType dataType = ((EDataTypeInSlotsKey) key).getEmfKey(); + + if (isIndexed(key, IndexingService.STATISTICS)) { + if (groupMask.indices.length == 0) { // empty projection + return (0 != baseIndex.countDataTypeInstances(dataType)) ? Optional.of(1L) : Optional.of(0L); + } else { // unprojected + return Optional.of((long)baseIndex.countDataTypeInstances(dataType)); + } + } else return Optional.empty(); + + } else if (key instanceof EStructuralFeatureInstancesKey) { + EStructuralFeatureInstancesKey featureKey = (EStructuralFeatureInstancesKey) key; + EStructuralFeature feature = featureKey.getEmfKey(); + + + boolean isSourceSelected = false; + boolean isTargetSelected = false; + for (int i = 0; i < groupMask.getSize(); i++) { + int index = groupMask.indices[i]; + if (index == 0) { + isSourceSelected = true; + } else if (index == 1) { + isTargetSelected = true; + } + } + + Optional sourceTypeUpperEstimate = + estimateCardinality(metaContext.getSourceTypeKey(featureKey), + TupleMask.identity(1), Accuracy.BEST_UPPER_BOUND); + Optional targetTypeUpperEstimate = + estimateCardinality(metaContext.getTargetTypeKey(featureKey), + TupleMask.identity(1), Accuracy.BEST_UPPER_BOUND); + + if (!isSourceSelected && !isTargetSelected) { // empty projection + if (isIndexed(key, IndexingService.STATISTICS)) { // we have exact node counts + return (0 == baseIndex.countFeatures(feature)) ? Optional.of(0L) : Optional.of(1L); + } else { // we can still say 0 in a few cases + if (0 == sourceTypeUpperEstimate.orElse(-1L)) + return Optional.of(0L); + + if (0 == targetTypeUpperEstimate.orElse(-1L)) + return Optional.of(0L); + + return Optional.empty(); + } + + } else if (isSourceSelected && !isTargetSelected) { // count sources + if (isIndexed(key, IndexingService.INSTANCES)) { // we have instances, therefore feature end counts + return Optional.of((long)(baseIndex.getHoldersOfFeature(feature).size())); + } else if (metaContext.isFeatureMultiplicityToOne(feature) && + isIndexed(key, IndexingService.STATISTICS)) { // count of edges = count of sources due to func. dep. + return Optional.of((long)(baseIndex.countFeatures(feature))); + } else if (Accuracy.BEST_UPPER_BOUND.atLeastAsPreciseAs(requiredAccuracy)) { + // upper bound by source type + Optional estimate = sourceTypeUpperEstimate; + // total edge counts are another upper bound (even if instances are unindexed) + if (isIndexed(key, IndexingService.STATISTICS)) { + estimate = Optional.of(Math.min( + baseIndex.countFeatures(feature), + estimate.orElse(Long.MAX_VALUE))); + } + return estimate; + } else return Optional.empty(); + + } else if (!isSourceSelected /*&& isTargetSelected*/) { // count targets + if (isIndexed(key, IndexingService.INSTANCES)) { // we have instances, therefore feature end counts + return Optional.of((long)(baseIndex.getValuesOfFeature(feature).size())); + } else if (metaContext.isFeatureMultiplicityOneTo(feature) && + isIndexed(key, IndexingService.STATISTICS)) { // count of edges = count of targets due to func. dep. + return Optional.of((long)(baseIndex.countFeatures(feature))); + } else if (Accuracy.BEST_UPPER_BOUND.atLeastAsPreciseAs(requiredAccuracy)) { // upper bound by target type + // upper bound by target type + Optional estimate = targetTypeUpperEstimate; + // total edge counts are another upper bound (even if instances are unindexed) + if (isIndexed(key, IndexingService.STATISTICS)) { + estimate = Optional.of(Math.min( + baseIndex.countFeatures(feature), + estimate.orElse(Long.MAX_VALUE))); + } + return estimate; + } else return Optional.empty(); + + } else { // (isSourceSelected && isTargetSelected) // count edges + if (isIndexed(key, IndexingService.STATISTICS)) { // we have exact edge counts + return Optional.of((long)baseIndex.countFeatures(feature)); + } else if (Accuracy.BEST_UPPER_BOUND.atLeastAsPreciseAs(requiredAccuracy)) { // overestimates may still be available + Optional estimate = // trivial upper bound: product of source & target type sizes (if available) + (sourceTypeUpperEstimate.isPresent() && targetTypeUpperEstimate.isPresent()) ? + Optional.of( + ((long)sourceTypeUpperEstimate.get()) * targetTypeUpperEstimate.get() + ) : Optional.empty(); + + if (metaContext.isFeatureMultiplicityToOne(feature) && sourceTypeUpperEstimate.isPresent()) { + // upper bounded by source type due to func. dep. + estimate = Optional.of(Math.min( + sourceTypeUpperEstimate.get(), + estimate.orElse(Long.MAX_VALUE))); + } + if (metaContext.isFeatureMultiplicityOneTo(feature) && targetTypeUpperEstimate.isPresent()) { + // upper bounded by target type due to func. dep. + estimate = Optional.of(Math.min( + targetTypeUpperEstimate.get(), + estimate.orElse(Long.MAX_VALUE))); + } + + return estimate; + } else return Optional.empty(); + } + + } else { + return Optional.empty(); + } + } + + /** + * @since 2.1 + */ + @Override + public Optional estimateAverageBucketSize(IInputKey key, TupleMask groupMask, Accuracy requiredAccuracy) { + // smart handling of special cases + if (key instanceof EStructuralFeatureInstancesKey) { + EStructuralFeatureInstancesKey featureKey = (EStructuralFeatureInstancesKey) key; + EStructuralFeature feature = featureKey.getEmfKey(); + + // special treatment for edge navigation + if (1 == groupMask.getSize()) { + if (0 == groupMask.indices[0] && metaContext.isFeatureMultiplicityToOne(feature)) { // count targets per source + return Optional.of(1.0); + } else if (1 == groupMask.indices[0] && metaContext.isFeatureMultiplicityOneTo(feature)) { // count sources per target + return Optional.of(1.0); + } + } + } + + // keep the default behaviour + return super.estimateAverageBucketSize(key, groupMask, requiredAccuracy); + } + + + public void ensureEnumerableKey(IInputKey key) { + ensureValidKey(key); + if (! metaContext.isEnumerable(key)) + throw new IllegalArgumentException("Key is not enumerable: " + key); + + } + + public void ensureValidKey(IInputKey key) { + metaContext.ensureValidKey(key); + } + public void illegalInputKey(IInputKey key) { + metaContext.illegalInputKey(key); + } + public void illegalEnumerateValues(Tuple seed) { + throw new IllegalArgumentException("Must have exactly one unseeded element in enumerateValues() invocation, received instead: " + seed); + } + + /** + * @since 1.4 + */ + public void ensureIndexed(EClass eClass, IndexingService service) { + if (addIndexingService(indexedClasses, eClass, service)) { + final Set newClasses = Collections.singleton(eClass); + IndexingLevel level = IndexingLevel.toLevel(service); + if (!baseIndex.getIndexingLevel(eClass).providesLevel(level)) { + baseIndex.registerEClasses(newClasses, level); + } + //baseIndex.addInstanceListener(newClasses, listener); + } + } + + /** + * @since 1.4 + */ + public void ensureIndexed(EDataType eDataType, IndexingService service) { + if (addIndexingService(indexedDataTypes, eDataType, service)) { + final Set newDataTypes = Collections.singleton(eDataType); + IndexingLevel level = IndexingLevel.toLevel(service); + if (!baseIndex.getIndexingLevel(eDataType).providesLevel(level)) { + baseIndex.registerEDataTypes(newDataTypes, level); + } + //baseIndex.addDataTypeListener(newDataTypes, listener); + } + } + + /** + * @since 1.4 + */ + public void ensureIndexed(EStructuralFeature feature, IndexingService service) { + if (addIndexingService(indexedFeatures, feature, service)) { + final Set newFeatures = Collections.singleton(feature); + IndexingLevel level = IndexingLevel.toLevel(service); + if (!baseIndex.getIndexingLevel(feature).providesLevel(level)) { + baseIndex.registerEStructuralFeatures(newFeatures, level); + } + //baseIndex.addFeatureListener(newFeatures, listener); + } + } + + + + // UPDATE HANDLING SECTION + + /** + * Abstract internal listener wrapper for a {@link IQueryRuntimeContextListener}. + * Due to the overridden equals/hashCode(), it is safe to create a new instance for the same listener. + * + * @author Bergmann Gabor + */ + private abstract static class ListenerAdapter { + IQueryRuntimeContextListener listener; + Tuple seed; + /** + * @param listener + * @param seed must be non-null + */ + public ListenerAdapter(IQueryRuntimeContextListener listener, Object... seed) { + this.listener = listener; + this.seed = Tuples.flatTupleOf(seed); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + + ((listener == null) ? 0 : listener.hashCode()); + result = prime * result + ((seed == null) ? 0 : seed.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj.getClass().equals(this.getClass()))) + return false; + ListenerAdapter other = (ListenerAdapter) obj; + if (listener == null) { + if (other.listener != null) + return false; + } else if (!listener.equals(other.listener)) + return false; + if (seed == null) { + if (other.seed != null) + return false; + } else if (!seed.equals(other.seed)) + return false; + return true; + } + + + @Override + public String toString() { + return "Wrapped#" + listener; + } + + + } + private static class EClassTransitiveInstancesAdapter extends ListenerAdapter implements InstanceListener { + private Object seedInstance; + public EClassTransitiveInstancesAdapter(IQueryRuntimeContextListener listener, Object seedInstance) { + super(listener, seedInstance); + this.seedInstance = seedInstance; + } + @Override + public void instanceInserted(EClass clazz, EObject instance) { + if (seedInstance != null && !seedInstance.equals(instance)) return; + listener.update(new EClassTransitiveInstancesKey(clazz), + Tuples.staticArityFlatTupleOf(instance), true); + } + @Override + public void instanceDeleted(EClass clazz, EObject instance) { + if (seedInstance != null && !seedInstance.equals(instance)) return; + listener.update(new EClassTransitiveInstancesKey(clazz), + Tuples.staticArityFlatTupleOf(instance), false); + } + } + private static class EDataTypeInSlotsAdapter extends ListenerAdapter implements DataTypeListener { + private Object seedValue; + public EDataTypeInSlotsAdapter(IQueryRuntimeContextListener listener, Object seedValue) { + super(listener, seedValue); + this.seedValue = seedValue; + } + @Override + public void dataTypeInstanceInserted(EDataType type, Object instance, + boolean firstOccurrence) { + if (firstOccurrence) { + if (seedValue != null && !seedValue.equals(instance)) return; + listener.update(new EDataTypeInSlotsKey(type), + Tuples.staticArityFlatTupleOf(instance), true); + } + } + @Override + public void dataTypeInstanceDeleted(EDataType type, Object instance, + boolean lastOccurrence) { + if (lastOccurrence) { + if (seedValue != null && !seedValue.equals(instance)) return; + listener.update(new EDataTypeInSlotsKey(type), + Tuples.staticArityFlatTupleOf(instance), false); + } + } + } + private static class EStructuralFeatureInstancesKeyAdapter extends ListenerAdapter implements FeatureListener { + private Object seedHost; + private Object seedValue; + public EStructuralFeatureInstancesKeyAdapter(IQueryRuntimeContextListener listener, Object seedHost, Object seedValue) { + super(listener, seedHost, seedValue); + this.seedHost = seedHost; + this.seedValue = seedValue; + } + @Override + public void featureInserted(EObject host, EStructuralFeature feature, + Object value) { + if (seedHost != null && !seedHost.equals(host)) return; + if (seedValue != null && !seedValue.equals(value)) return; + listener.update(new EStructuralFeatureInstancesKey(feature), + Tuples.staticArityFlatTupleOf(host, value), true); + } + @Override + public void featureDeleted(EObject host, EStructuralFeature feature, + Object value) { + if (seedHost != null && !seedHost.equals(host)) return; + if (seedValue != null && !seedValue.equals(value)) return; + listener.update(new EStructuralFeatureInstancesKey(feature), + Tuples.staticArityFlatTupleOf(host, value), false); + } + } + + @Override + public void addUpdateListener(IInputKey key, Tuple seed /* TODO ignored */, IQueryRuntimeContextListener listener) { + // stateless, so NOP + if (key instanceof JavaTransitiveInstancesKey) return; + + ensureIndexed(key, IndexingService.INSTANCES); + if (key instanceof EClassTransitiveInstancesKey) { + EClass eClass = ((EClassTransitiveInstancesKey) key).getEmfKey(); + baseIndex.addInstanceListener(Collections.singleton(eClass), + new EClassTransitiveInstancesAdapter(listener, seed.get(0))); + } else if (key instanceof EDataTypeInSlotsKey) { + EDataType dataType = ((EDataTypeInSlotsKey) key).getEmfKey(); + baseIndex.addDataTypeListener(Collections.singleton(dataType), + new EDataTypeInSlotsAdapter(listener, seed.get(0))); + } else if (key instanceof EStructuralFeatureInstancesKey) { + EStructuralFeature feature = ((EStructuralFeatureInstancesKey) key).getEmfKey(); + baseIndex.addFeatureListener(Collections.singleton(feature), + new EStructuralFeatureInstancesKeyAdapter(listener, seed.get(0), seed.get(1))); + } else { + illegalInputKey(key); + } + } + @Override + public void removeUpdateListener(IInputKey key, Tuple seed, IQueryRuntimeContextListener listener) { + // stateless, so NOP + if (key instanceof JavaTransitiveInstancesKey) return; + + ensureIndexed(key, IndexingService.INSTANCES); + if (key instanceof EClassTransitiveInstancesKey) { + EClass eClass = ((EClassTransitiveInstancesKey) key).getEmfKey(); + baseIndex.removeInstanceListener(Collections.singleton(eClass), + new EClassTransitiveInstancesAdapter(listener, seed.get(0))); + } else if (key instanceof EDataTypeInSlotsKey) { + EDataType dataType = ((EDataTypeInSlotsKey) key).getEmfKey(); + baseIndex.removeDataTypeListener(Collections.singleton(dataType), + new EDataTypeInSlotsAdapter(listener, seed.get(0))); + } else if (key instanceof EStructuralFeatureInstancesKey) { + EStructuralFeature feature = ((EStructuralFeatureInstancesKey) key).getEmfKey(); + baseIndex.removeFeatureListener(Collections.singleton(feature), + new EStructuralFeatureInstancesKeyAdapter(listener, seed.get(0), seed.get(1))); + } else { + illegalInputKey(key); + } + } + + // TODO wrap / unwrap enum literals + // TODO use this in all other public methods (maybe wrap & delegate?) + + @Override + public Object unwrapElement(Object internalElement) { + return internalElement; + } + @Override + public Tuple unwrapTuple(Tuple internalElements) { + return internalElements; + } + @Override + public Object wrapElement(Object externalElement) { + return externalElement; + } + @Override + public Tuple wrapTuple(Tuple externalElements) { + return externalElements; + } + + /** + * @since 1.4 + */ + @Override + public void ensureWildcardIndexing(IndexingService service) { + baseIndex.setWildcardLevel(IndexingLevel.toLevel(service)); + } + + /** + * @since 1.4 + */ + @Override + public void executeAfterTraversal(Runnable runnable) throws InvocationTargetException { + baseIndex.executeAfterTraversal(runnable); + } +} + diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/EMFScope.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/EMFScope.java new file mode 100644 index 00000000..dead9716 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/EMFScope.java @@ -0,0 +1,199 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Denes Harmath, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.emf; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import org.apache.log4j.Logger; +import org.eclipse.emf.common.notify.Notifier; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.ResourceSet; +import tools.refinery.viatra.runtime.api.AdvancedViatraQueryEngine; +import tools.refinery.viatra.runtime.api.ViatraQueryEngine; +import tools.refinery.viatra.runtime.api.scope.IEngineContext; +import tools.refinery.viatra.runtime.api.scope.IIndexingErrorListener; +import tools.refinery.viatra.runtime.api.scope.QueryScope; +import tools.refinery.viatra.runtime.base.api.BaseIndexOptions; +import tools.refinery.viatra.runtime.base.api.NavigationHelper; +import tools.refinery.viatra.runtime.exception.ViatraQueryException; + +/** + * An {@link QueryScope} consisting of EMF objects contained in multiple {@link ResourceSet}s, a single {@link ResourceSet}, {@link Resource} or a containment subtree below a given {@link EObject}. + * + *

    The scope is characterized by a root and some options (see {@link BaseIndexOptions}) such as dynamic EMF mode, subtree filtering etc. + *

    + * The scope of pattern matching will be the given EMF model root(s) and below (see FAQ for more precise definition). + * + *

    Note on cross-resource containment: in case of {@link EObject} scopes, cross-resource containments will be considered part of the scope. + * The same goes for {@link ResourceSet} scopes, provided that the resource of the contained element is successfully loaded into the resource set. + * In case of a {@link Resource} scope, containments pointing out from the resource will be excluded from the scope. + * Thus the boundaries of {@link EObject} scopes conform to the notion of EMF logical containment, while {@link Resource} and {@link ResourceSet} scopes adhere to persistence boundaries. + * + * @author Bergmann Gabor + * + */ +public class EMFScope extends QueryScope { + + private Set scopeRoots; + private BaseIndexOptions options; + + /** + * Creates an EMF scope at the given root, with default options (recommended for most users). + * @param scopeRoot the root of the EMF scope + * @throws ViatraQueryRuntimeException- if scopeRoot is not an EMF ResourceSet, Resource or EObject + */ + public EMFScope(Notifier scopeRoot) { + this(Collections.singleton(scopeRoot), new BaseIndexOptions()); + } + + /** + * Creates an EMF scope at the given root, with customizable options. + *

    Most users should consider {@link #EMFScope(Notifier)} instead. + * @param scopeRoot the root of the EMF scope + * @param options the base index building settings + * @throws ViatraQueryRuntimeException if scopeRoot is not an EMF ResourceSet, Resource or EObject + */ + public EMFScope(Notifier scopeRoot, BaseIndexOptions options) { + this(Collections.singleton(scopeRoot), options); + } + + /** + * Creates an EMF scope at the given roots, with default options (recommended for most users). + * @param scopeRoots the roots of the EMF scope, must be {@link ResourceSet}s + * @throws ViatraQueryRuntimeException if not all scopeRoots are {@link ResourceSet}s + */ + public EMFScope(Set scopeRoots) { + this(scopeRoots, new BaseIndexOptions()); + } + + /** + * Creates an EMF scope at the given roots, with customizable options. + *

    Most users should consider {@link #EMFScope(Set)} instead. + * @param scopeRoots the roots of the EMF scope, must be {@link ResourceSet}s + * @param options the base index building settings + * @throws ViatraQueryRuntimeException if not all scopeRoots are {@link ResourceSet}s + */ + public EMFScope(Set scopeRoots, BaseIndexOptions options) { + super(); + if (scopeRoots.isEmpty()) { + throw new IllegalArgumentException("No scope roots given"); + } else if (scopeRoots.size() == 1) { + checkScopeRoots(scopeRoots, EObject.class::isInstance, Resource.class::isInstance, ResourceSet.class::isInstance); + } else { + checkScopeRoots(scopeRoots, ResourceSet.class::isInstance); + } + this.scopeRoots = new HashSet<>(scopeRoots); + this.options = options.copy(); + } + + @SafeVarargs + private final void checkScopeRoots(Set scopeRoots, Predicate... predicates) { + for (Notifier scopeRoot : scopeRoots) { + // Creating compound predicate that checks the various branches of disjunction together + Predicate compoundPredicate = Arrays.stream(predicates).collect(Collectors.reducing(a -> true, a -> a, (a, b) -> a.or(b))); + if (!compoundPredicate.test(scopeRoot)) + throw new ViatraQueryException(ViatraQueryException.INVALID_EMFROOT + + (scopeRoot == null ? "(null)" : scopeRoot.getClass().getName()), + ViatraQueryException.INVALID_EMFROOT_SHORT); + } + } + + /** + * @return the scope roots ({@link ResourceSet}s) containing the model + */ + public Set getScopeRoots() { + return scopeRoots; + } + + /** + * @return the options + */ + public BaseIndexOptions getOptions() { + return options.copy(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((options == null) ? 0 : options.hashCode()); + result = prime * result + + ((scopeRoots == null) ? 0 : scopeRoots.hashCode()); + return result; + } + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof EMFScope)) + return false; + EMFScope other = (EMFScope) obj; + if (options == null) { + if (other.options != null) + return false; + } else if (!options.equals(other.options)) + return false; + if (scopeRoots == null) { + if (other.scopeRoots != null) + return false; + } else if (!scopeRoots.equals(other.scopeRoots)) + return false; + return true; + } + + + @Override + public String toString() { + return String.format("EMFScope(%s):%s", options, scopeRoots.stream().map(this::scopeRootString).collect(Collectors.joining(","))); + } + + private String scopeRootString(Notifier notifier) { + if (notifier instanceof Resource) { + Resource resource = (Resource) notifier; + return String.format("%s(%s)", resource.getClass(), resource.getURI()); + } else if (notifier instanceof ResourceSet) { + ResourceSet resourceSet = (ResourceSet) notifier; + return resourceSet.getResources().stream() + .map(Resource::getURI) + .map(URI::toString) + .collect(Collectors.joining(", ", resourceSet.getClass() + "(", ")")); + } else { + return notifier.toString(); + } + } + + @Override + protected IEngineContext createEngineContext(ViatraQueryEngine engine, IIndexingErrorListener errorListener, Logger logger) { + return new EMFEngineContext(this, engine, errorListener, logger); + } + + /** + * Provides access to the underlying EMF model index ({@link NavigationHelper}) from a VIATRA Query engine instantiated on an EMFScope + * + * @param engine an already existing VIATRA Query engine instantiated on an EMFScope + * @return the underlying EMF base index that indexes the contents of the EMF model + * @throws ViatraQueryRuntimeException if base index initialization fails + */ + public static NavigationHelper extractUnderlyingEMFIndex(ViatraQueryEngine engine) { + final QueryScope scope = engine.getScope(); + if (scope instanceof EMFScope) + return ((EMFBaseIndexWrapper)AdvancedViatraQueryEngine.from(engine).getBaseIndex()).getNavigationHelper(); + else throw new IllegalArgumentException("Cannot extract EMF base index from VIATRA Query engine instantiated on non-EMF scope " + scope); + } + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/helper/ViatraQueryRuntimeHelper.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/helper/ViatraQueryRuntimeHelper.java new file mode 100644 index 00000000..93ac97f2 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/helper/ViatraQueryRuntimeHelper.java @@ -0,0 +1,161 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Abel Hegedus, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.emf.helper; + +import java.util.function.Function; + +import org.eclipse.emf.ecore.EClassifier; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.EStructuralFeature; +import tools.refinery.viatra.runtime.api.IPatternMatch; + +/** + * Helper functions for dealing with the EMF objects with VIATRA Queries. + * + * @author Abel Hegedus + * @since 0.9 + * + */ +public class ViatraQueryRuntimeHelper { + + private ViatraQueryRuntimeHelper() {/*Utility class constructor*/} + + private static final Function STRING_VALUE_TRANSFORMER = input -> (input == null) ? "(null)" : input.toString(); + + /** + * Gives a human-readable name of an EMF type. + */ + public static String prettyPrintEMFType(Object typeObject) { + if (typeObject == null) { + return "(null)"; + } else if (typeObject instanceof EClassifier) { + final EClassifier eClassifier = (EClassifier) typeObject; + final EPackage ePackage = eClassifier.getEPackage(); + final String nsURI = ePackage == null ? null : ePackage.getNsURI(); + final String typeName = eClassifier.getName(); + return "" + nsURI + "/" + typeName; + } else if (typeObject instanceof EStructuralFeature) { + final EStructuralFeature feature = (EStructuralFeature) typeObject; + return prettyPrintEMFType(feature.getEContainingClass()) + "." + feature.getName(); + } else + return typeObject.toString(); + } + + + /** + * Get the structural feature with the given name of the given object. + * + * @param o + * the object (must be an EObject) + * @param featureName + * the name of the feature + * @return the EStructuralFeature of the object or null if it can not be found + */ + public static EStructuralFeature getFeature(Object o, String featureName) { + if (o instanceof EObject) { + EStructuralFeature feature = ((EObject) o).eClass().getEStructuralFeature(featureName); + return feature; + } + return null; + } + + /** + * Returns the message for the given match using the given format. The format string can refer to the value of + * parameter A of the match with $A$ and even access features of A (if it's an EObject), e.g. $A.id$. + * + *

    + * If the selected parameter does not exist, the string "[no such parameter]" is added + * + *

    + * If no feature is defined, but A has a feature called "name", then its value is used. + * + *

    + * If the selected feature does not exist, A.toString() is added. + * + *

    + * If the selected feature is null, the string "null" is added. + * + * @param match + * cannot be null! + * @param messageFormat + * cannot be null! + */ + public static String getMessage(IPatternMatch match, String messageFormat) { + return getMessage(match, messageFormat, STRING_VALUE_TRANSFORMER); + } + + /** + * Returns the message for the given match using the given format while transforming values with the given function. + * The format string can refer to the value of parameter A of the match with $A$ and even access features of A (if + * it's an EObject), e.g. $A.id$. The function will be called to compute the final string representation of the + * values selected by the message format. + * + *

    + * If the selected parameter does not exist, the string "[no such parameter]" is added + * + *

    + * If no feature is defined, but A has a feature called "name", then its value is passed to the function. + * + *

    + * If the selected feature does not exist, A is passed to the function. + * + *

    + * If the selected feature is null, the string "null" is added. + * + * @param match + * cannot be null! + * @param messageFormat + * cannot be null! + * @param parameterValueTransformer + * cannot be null! + * @since 2.0 + */ + public static String getMessage(IPatternMatch match, String messageFormat, Function parameterValueTransformer) { + String[] tokens = messageFormat.split("\\$"); + StringBuilder newText = new StringBuilder(); + + for (int i = 0; i < tokens.length; i++) { + if (i % 2 == 0) { + newText.append(tokens[i]); + } else { + String[] objectTokens = tokens[i].split("\\."); + if (objectTokens.length > 0) { + Object o = null; + EStructuralFeature feature = null; + + if (objectTokens.length == 1) { + o = match.get(objectTokens[0]); + feature = getFeature(o, "name"); + } + if (objectTokens.length == 2) { + o = match.get(objectTokens[0]); + feature = getFeature(o, objectTokens[1]); + } + + if (o != null && feature != null) { + Object value = ((EObject) o).eGet(feature); + if (value != null) { + newText.append(parameterValueTransformer.apply(value)); + } else { + newText.append("null"); + } + } else if (o != null) { + newText.append(parameterValueTransformer.apply(o)); + } + } else { + newText.append("[no such parameter]"); + } + } + } + + return newText.toString(); + } + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/types/BaseEMFTypeKey.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/types/BaseEMFTypeKey.java new file mode 100644 index 00000000..c5dfa966 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/types/BaseEMFTypeKey.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.emf.types; + +import tools.refinery.viatra.runtime.matchers.context.common.BaseInputKeyWrapper; + +/** + * Base class for EMF Type keys. + * @author Bergmann Gabor + * + */ +public abstract class BaseEMFTypeKey extends BaseInputKeyWrapper { + + public BaseEMFTypeKey(EMFKey emfKey) { + super(emfKey); + } + + public EMFKey getEmfKey() { + return getWrappedKey(); + } + + @Override + public String toString() { + return this.getPrettyPrintableName(); + } + + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/types/EClassExactInstancesKey.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/types/EClassExactInstancesKey.java new file mode 100644 index 00000000..dd10502d --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/types/EClassExactInstancesKey.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.emf.types; + +import org.eclipse.emf.ecore.EClass; +import tools.refinery.viatra.runtime.emf.EMFScope; +import tools.refinery.viatra.runtime.emf.helper.ViatraQueryRuntimeHelper; + +/** + * Instance tuples are of form (x), where x is an eObject instance of the given eClass, but not one of its subclasses, within the scope. + *

    This input key has the strict semantics that instances must be within the scope. + * + * @noreference This class is not intended to be referenced by clients. Not currently supported by {@link EMFScope}, for internal use only at the time + * + * @author Bergmann Gabor + * @since 2.1 + */ +public class EClassExactInstancesKey extends BaseEMFTypeKey { + + public EClassExactInstancesKey(EClass emfKey) { + super(emfKey); + } + + @Override + public String getPrettyPrintableName() { + return "(scoped,exact) "+ViatraQueryRuntimeHelper.prettyPrintEMFType(wrappedKey); + } + + @Override + public String getStringID() { + return "eClass(scoped,exact)#"+ ViatraQueryRuntimeHelper.prettyPrintEMFType(wrappedKey); + } + + @Override + public int getArity() { + return 1; + } + + @Override + public boolean isEnumerable() { + return true; + } + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/types/EClassTransitiveInstancesKey.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/types/EClassTransitiveInstancesKey.java new file mode 100644 index 00000000..4ca6b220 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/types/EClassTransitiveInstancesKey.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.emf.types; + +import org.eclipse.emf.ecore.EClass; +import tools.refinery.viatra.runtime.emf.EMFScope; +import tools.refinery.viatra.runtime.emf.helper.ViatraQueryRuntimeHelper; + +/** + * Instance tuples are of form (x), where x is an eObject instance of the given eClass or one of its subclasses within the scope. + *

    As of version 1.6, this input key has the strict semantics that instances must be within the {@link EMFScope}. + * @author Bergmann Gabor + * + */ +public class EClassTransitiveInstancesKey extends BaseEMFTypeKey { + + public EClassTransitiveInstancesKey(EClass emfKey) { + super(emfKey); + } + + @Override + public String getPrettyPrintableName() { + return "(scoped) "+ViatraQueryRuntimeHelper.prettyPrintEMFType(wrappedKey); + } + + @Override + public String getStringID() { + return "eClass(scoped)#"+ ViatraQueryRuntimeHelper.prettyPrintEMFType(wrappedKey); + } + + @Override + public int getArity() { + return 1; + } + + @Override + public boolean isEnumerable() { + return true; + } + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/types/EClassUnscopedTransitiveInstancesKey.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/types/EClassUnscopedTransitiveInstancesKey.java new file mode 100644 index 00000000..11c5b235 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/types/EClassUnscopedTransitiveInstancesKey.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2010-2017, Gabor Bergmann, IncQueryLabs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.emf.types; + +import org.eclipse.emf.ecore.EClass; +import tools.refinery.viatra.runtime.emf.helper.ViatraQueryRuntimeHelper; + +/** + * Instance tuples are of form (x), where x is an eObject instance of the given eClass or one of its subclasses regardless whether it is within the scope. + * + * @author Bergmann Gabor + * @since 1.6 + */ +public class EClassUnscopedTransitiveInstancesKey extends BaseEMFTypeKey { + + public EClassUnscopedTransitiveInstancesKey(EClass emfKey) { + super(emfKey); + } + + @Override + public String getPrettyPrintableName() { + return "(unscoped) "+ViatraQueryRuntimeHelper.prettyPrintEMFType(wrappedKey); + } + + @Override + public String getStringID() { + return "eClass(unscoped)#"+ ViatraQueryRuntimeHelper.prettyPrintEMFType(wrappedKey); + } + + @Override + public int getArity() { + return 1; + } + + @Override + public boolean isEnumerable() { + return false; + } + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/types/EDataTypeInSlotsKey.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/types/EDataTypeInSlotsKey.java new file mode 100644 index 00000000..a1cc4863 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/types/EDataTypeInSlotsKey.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.emf.types; + +import org.eclipse.emf.ecore.EDataType; +import tools.refinery.viatra.runtime.emf.helper.ViatraQueryRuntimeHelper; + +/** + * Instance tuples are of form (x), where x is an instance of the given eDataType residing at an attribute slot of an eObject in the model. + * @author Bergmann Gabor + * + */ +public class EDataTypeInSlotsKey extends BaseEMFTypeKey { + + /** + * @param emfKey + */ + public EDataTypeInSlotsKey(EDataType emfKey) { + super(emfKey); + } + + @Override + public String getPrettyPrintableName() { + return "(Attribute Slot Values: " + ViatraQueryRuntimeHelper.prettyPrintEMFType(wrappedKey) + ")"; + } + + @Override + public String getStringID() { + return "slotValue#" + ViatraQueryRuntimeHelper.prettyPrintEMFType(wrappedKey); + } + + @Override + public int getArity() { + return 1; + } + + @Override + public boolean isEnumerable() { + return true; + } + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/types/EStructuralFeatureInstancesKey.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/types/EStructuralFeatureInstancesKey.java new file mode 100644 index 00000000..357f5e7e --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/emf/types/EStructuralFeatureInstancesKey.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.emf.types; + +import org.eclipse.emf.ecore.EStructuralFeature; +import tools.refinery.viatra.runtime.emf.EMFScope; +import tools.refinery.viatra.runtime.emf.helper.ViatraQueryRuntimeHelper; + +/** + * Instance tuples are of form (x, y), where x is an eObject that has y as the value of the given feature (or one of the values in case of multi-valued). + * + *

    As of version 1.6, this input key has the strict semantics that x must be within the {@link EMFScope}, scoping is not implied for y. + * @author Bergmann Gabor + * + */ +public class EStructuralFeatureInstancesKey extends BaseEMFTypeKey { + + public EStructuralFeatureInstancesKey(EStructuralFeature emfKey) { + super(emfKey); + } + + @Override + public String getPrettyPrintableName() { + return ViatraQueryRuntimeHelper.prettyPrintEMFType(wrappedKey); + } + + @Override + public String getStringID() { + return "feature#"+ getPrettyPrintableName(); + } + + @Override + public int getArity() { + return 2; + } + + @Override + public boolean isEnumerable() { + return true; + } + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/exception/ViatraQueryException.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/exception/ViatraQueryException.java new file mode 100644 index 00000000..fec547a6 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/exception/ViatraQueryException.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Akos Horvath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.exception; + +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; +import tools.refinery.viatra.runtime.matchers.planning.QueryProcessingException; +import tools.refinery.viatra.runtime.matchers.psystem.queries.QueryInitializationException; + +/** + * A general VIATRA Query-related problem during the operation of the VIATRA Query + * engine, or the loading, manipulation and evaluation of queries. + * + * @author Bergmann Gabor + * @since 0.9 + * + */ +public class ViatraQueryException extends ViatraQueryRuntimeException { + + private static final long serialVersionUID = -74252748358355750L; + + public static final String PARAM_NOT_SUITABLE_WITH_NO = "The type of the parameters are not suitable for the operation. Parameter number: "; + public static final String CONVERSION_FAILED = "Could not convert the term to the designated type"; + public static final String CONVERT_NULL_PARAMETER = "Could not convert null to the designated type"; + public static final String RELATIONAL_PARAM_UNSUITABLE = "The parameters are not acceptable by the operation"; + /** + * @since 0.9 + */ + public static final String PROCESSING_PROBLEM = "The following error occurred during the processing of a query (e.g. for the preparation of a VIATRA pattern matcher)"; + /** + * @since 0.9 + */ + public static final String QUERY_INIT_PROBLEM = "The following error occurred during the initialization of a VIATRA query specification"; + public static final String GETNAME_FAILED = "Could not get 'name' attribute of the result"; + + public static final String INVALID_EMFROOT = "Incremental EMF query engine can only be attached on the contents of an EMF EObject, Resource, ResourceSet or multiple ResourceSets. Received instead: "; + public static final String INVALID_EMFROOT_SHORT = "Invalid EMF model root"; + // public static final String EMF_MODEL_PROCESSING_ERROR = "Error while processing the EMF model"; + + private final String shortMessage; + + public ViatraQueryException(String s, String shortMessage) { + super(s); + this.shortMessage = shortMessage; + } + + public ViatraQueryException(QueryProcessingException e) { + super(PROCESSING_PROBLEM + ": " + e.getMessage(), e); + this.shortMessage = e.getShortMessage(); + } + + public ViatraQueryException(QueryInitializationException e) { + super(QUERY_INIT_PROBLEM + ": " + e.getMessage(), e); + this.shortMessage = e.getShortMessage(); + } + + public ViatraQueryException(String s, String shortMessage, Throwable e) { + super(s + ": " + e.getMessage(), e); + this.shortMessage = shortMessage; + } + + public String getShortMessage() { + return shortMessage; + } + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/extensibility/IQueryGroupProvider.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/extensibility/IQueryGroupProvider.java new file mode 100644 index 00000000..45594b5b --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/extensibility/IQueryGroupProvider.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Abel Hegedus, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.extensibility; + +import java.util.Set; + +import tools.refinery.viatra.runtime.api.IQueryGroup; +import tools.refinery.viatra.runtime.matchers.util.IProvider; + +/** + * Provider interface for {@link IQueryGroup} instances with added method for + * requesting the set of FQNs for the query specifications in the group. + * + * @author Abel Hegedus + * @since 1.3 + * + */ +public interface IQueryGroupProvider extends IProvider { + + /** + * Note that the provider should load the query group class only if the FQNs can not be computed in other ways. + * + * @return the set of query specification FQNs in the group + */ + Set getQuerySpecificationFQNs(); + + /** + * Note that the provider should load the query group class only if the FQNs can not be computed in other ways. + * + * @return a set of providers for query specifications in the group + */ + Set getQuerySpecificationProviders(); + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/extensibility/IQuerySpecificationProvider.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/extensibility/IQuerySpecificationProvider.java new file mode 100644 index 00000000..3c9c235d --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/extensibility/IQuerySpecificationProvider.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Abel Hegedus, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.extensibility; + +import tools.refinery.viatra.runtime.api.IQuerySpecification; +import tools.refinery.viatra.runtime.matchers.util.IProvider; + +/** + * Provider interface for {@link IQuerySpecification} instances with added method for + * requesting the FQN for the query specification. + * + * @author Abel Hegedus + * @since 1.3 + * + */ +public interface IQuerySpecificationProvider extends IProvider> { + + /** + * Note that the provider will usually not load the query specification class to return the FQN. + * + * @return the fully qualified name of the provided query specification + */ + String getFullyQualifiedName(); + + /** + * Returns the name of project providing the specification (or null if not calculable) + */ + String getSourceProjectName(); + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/extensibility/PQueryExtensionFactory.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/extensibility/PQueryExtensionFactory.java new file mode 100644 index 00000000..91e087d7 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/extensibility/PQueryExtensionFactory.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.extensibility; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import tools.refinery.viatra.runtime.api.IQuerySpecification; + +/** + * An extension factory to access PQuery instances from Query Specifications. + * + * @author Zoltan Ujhelyi + * + */ +public class PQueryExtensionFactory extends SingletonExtensionFactory { + + @Override + public Object create() throws CoreException { + final Object _spec = super.create(); + if (_spec instanceof IQuerySpecification) { + return ((IQuerySpecification) _spec).getInternalQueryRepresentation(); + } + throw new CoreException(new Status(IStatus.ERROR, getBundle().getSymbolicName(), "Cannot instantiate PQuery instance.")); + } + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/extensibility/SingletonExtensionFactory.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/extensibility/SingletonExtensionFactory.java new file mode 100644 index 00000000..29705968 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/extensibility/SingletonExtensionFactory.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.extensibility; + +import java.lang.reflect.Method; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.IExecutableExtension; +import org.eclipse.core.runtime.IExecutableExtensionFactory; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.Status; +import org.osgi.framework.Bundle; + +/** + * Factory to register a static singleton instance in an extension point. + * + * @author Zoltan Ujhelyi + * + */ +public class SingletonExtensionFactory implements IExecutableExtension, IExecutableExtensionFactory { + + private String clazzName; + private Bundle bundle; + + protected Bundle getBundle() { + return bundle; + } + + @Override + public Object create() throws CoreException { + try { + final Class clazz = bundle.loadClass(clazzName); + Method method = clazz.getMethod("instance"); + return method.invoke(null); + } catch (Exception e) { + throw new CoreException(new Status(IStatus.ERROR, bundle.getSymbolicName(), "Error loading group " + + clazzName, e)); + } + } + + @Override + public void setInitializationData(IConfigurationElement config, String propertyName, Object data) + throws CoreException { + String id = config.getContributor().getName(); + bundle = Platform.getBundle(id); + if (data instanceof String) { + clazzName = (String) data; + } else { + throw new CoreException(new Status(IStatus.ERROR, bundle.getSymbolicName(), + "Unsupported extension initialization data: " + data)); + } + } + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/extensibility/SingletonQueryGroupProvider.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/extensibility/SingletonQueryGroupProvider.java new file mode 100644 index 00000000..758b51dd --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/extensibility/SingletonQueryGroupProvider.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Abel Hegedus, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.extensibility; + +import java.util.Set; +import java.util.stream.Collectors; + +import tools.refinery.viatra.runtime.api.IQueryGroup; +import tools.refinery.viatra.runtime.api.IQuerySpecification; +import tools.refinery.viatra.runtime.matchers.util.SingletonInstanceProvider; + +/** + * Provider implementation for storing an existing query group instance. + * + * @author Abel Hegedus + * @since 1.3 + * + */ +public class SingletonQueryGroupProvider extends SingletonInstanceProvider implements IQueryGroupProvider { + + /** + * @param instance the instance to wrap + */ + public SingletonQueryGroupProvider(IQueryGroup instance) { + super(instance); + } + + @Override + public Set getQuerySpecificationFQNs() { + return get().getSpecifications().stream().map(IQuerySpecification::getFullyQualifiedName) + .collect(Collectors.toSet()); + } + + @Override + public Set getQuerySpecificationProviders() { + return get().getSpecifications().stream().map(SingletonQuerySpecificationProvider::new) + .collect(Collectors.toSet()); + } + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/extensibility/SingletonQuerySpecificationProvider.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/extensibility/SingletonQuerySpecificationProvider.java new file mode 100644 index 00000000..f8f3a741 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/extensibility/SingletonQuerySpecificationProvider.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Abel Hegedus, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.extensibility; + +import tools.refinery.viatra.runtime.api.IQuerySpecification; +import tools.refinery.viatra.runtime.matchers.util.SingletonInstanceProvider; + +/** + * Provider implementation for storing an existing query specification instance. + * + * @author Abel Hegedus + * @since 1.3 + * + */ +public class SingletonQuerySpecificationProvider extends SingletonInstanceProvider> + implements IQuerySpecificationProvider { + + /** + * + * @param instance the instance to wrap + */ + public SingletonQuerySpecificationProvider(IQuerySpecification instance) { + super(instance); + } + + @Override + public String getFullyQualifiedName() { + return get().getFullyQualifiedName(); + } + + @Override + public String getSourceProjectName() { + return null; + } + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/extensibility/ViatraQueryRuntimeConstants.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/extensibility/ViatraQueryRuntimeConstants.java new file mode 100644 index 00000000..9b0850e4 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/extensibility/ViatraQueryRuntimeConstants.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Abel Hegedus, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.extensibility; + +/** + * Utility class for Viatra Query runtime constants, such as extension point identifiers. + * + * @author Abel Hegedus + * + */ +public final class ViatraQueryRuntimeConstants { + + private ViatraQueryRuntimeConstants() {/* Constructor hidden for utility class */} + + // Surrogate query extension + + public static final String SURROGATE_QUERY_EXTENSIONID = "tools.refinery.viatra.runtime.surrogatequeryemf"; + + + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/ExtensionBasedSurrogateQueryLoader.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/ExtensionBasedSurrogateQueryLoader.java new file mode 100644 index 00000000..af7cdaf1 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/ExtensionBasedSurrogateQueryLoader.java @@ -0,0 +1,148 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, Abel Hegedus, Zoltan Ujhelyi, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.internal; + +import static tools.refinery.viatra.runtime.matchers.util.Preconditions.checkState; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.log4j.Logger; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.Platform; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EClassifier; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.EStructuralFeature; +import tools.refinery.viatra.runtime.emf.types.EStructuralFeatureInstancesKey; +import tools.refinery.viatra.runtime.extensibility.ViatraQueryRuntimeConstants; +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.context.surrogate.SurrogateQueryRegistry; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.util.IProvider; + +/** + * @author Abel Hegedus + * + */ +public class ExtensionBasedSurrogateQueryLoader { + + private static final String DUPLICATE_SURROGATE_QUERY = "Duplicate surrogate query definition %s for feature %s of EClass %s in package %s (FQN in map %s, contributing plug-ins %s, plug-in %s)"; + + private Map contributingPluginOfFeatureMap = new HashMap<>(); + private Map contributedSurrogateQueries; + + private static final ExtensionBasedSurrogateQueryLoader INSTANCE = new ExtensionBasedSurrogateQueryLoader(); + + /** + * A provider implementation for PQuery instances based on extension elements. It is expected that the getter will only + * @author Zoltan Ujhelyi + * + */ + private static final class PQueryProvider implements IProvider { + + private final IConfigurationElement element; + private PQuery query; + + public PQueryProvider(IConfigurationElement element) { + this.element = element; + this.query = null; + } + + @Override + public PQuery get() { + try { + if (query == null) { + query = (PQuery) element.createExecutableExtension("surrogate-query"); + } + return query; + } catch (CoreException e) { + throw new IllegalArgumentException("Error initializing surrogate query", e); + } + } + } + + public static ExtensionBasedSurrogateQueryLoader instance() { + return INSTANCE; + } + + public void loadKnownSurrogateQueriesIntoRegistry() { + Map knownSurrogateQueryFQNs = getSurrogateQueryProviders(); + for (Entry entry : knownSurrogateQueryFQNs.entrySet()) { + final IInputKey inputKey = new EStructuralFeatureInstancesKey(entry.getKey()); + SurrogateQueryRegistry.instance().registerSurrogateQueryForFeature(inputKey, entry.getValue()); + } + } + + private Map getSurrogateQueryProviders() { + if(contributedSurrogateQueries != null) { + return contributedSurrogateQueries; + } + contributedSurrogateQueries = new HashMap<>(); + if (Platform.isRunning()) { + for (IConfigurationElement e : Platform.getExtensionRegistry().getConfigurationElementsFor(ViatraQueryRuntimeConstants.SURROGATE_QUERY_EXTENSIONID)) { + if (e.isValid()) { + processExtension(e); + } + } + } + return contributedSurrogateQueries; + } + + private void processExtension(IConfigurationElement el) { + + try { + String packageUri = el.getAttribute("package-nsUri"); + String className = el.getAttribute("class-name"); + String featureName = el.getAttribute("feature-name"); + String queryFqn = el.getAttribute("query-fqn"); + if (queryFqn == null) { + queryFqn = ""; + } + PQueryProvider surrogateQueryProvider = new PQueryProvider(el); + + String contributorName = el.getContributor().getName(); + StringBuilder featureIdBuilder = new StringBuilder(); + checkState(packageUri != null, "Package NsURI cannot be null in extension"); + checkState(className != null, "Class name cannot be null in extension"); + checkState(featureName != null, "Feature name cannot be null in extension"); + + EPackage pckg = EPackage.Registry.INSTANCE.getEPackage(packageUri); + featureIdBuilder.append(packageUri); + checkState(pckg != null, "Package %s not found! (plug-in %s)", packageUri, contributorName); + + EClassifier clsr = pckg.getEClassifier(className); + featureIdBuilder.append("##").append(className); + checkState(clsr instanceof EClass, "EClassifier %s does not exist in package %s! (plug-in %s)", className, packageUri, contributorName); + + EClass cls = (EClass) clsr; + EStructuralFeature feature = cls.getEStructuralFeature(featureName); + featureIdBuilder.append("##").append(featureName); + checkState(feature != null, "Feature %s of EClass %s in package %s not found! (plug-in %s)", featureName, className, packageUri, contributorName); + + PQueryProvider fqnInMap = contributedSurrogateQueries.get(feature); + if(fqnInMap != null) { + String duplicateSurrogateFormatString = DUPLICATE_SURROGATE_QUERY; + Collection contributorPlugins = Arrays.asList(contributorName, contributingPluginOfFeatureMap.get(featureIdBuilder.toString())); + String duplicateSurrogateMessage = String.format(duplicateSurrogateFormatString, queryFqn, featureName, + className, packageUri, fqnInMap, contributorPlugins, contributorName); + throw new IllegalStateException(duplicateSurrogateMessage); + } + contributedSurrogateQueries.put(feature, surrogateQueryProvider); + contributingPluginOfFeatureMap.put(featureIdBuilder.toString(), contributorName); + } catch (Exception e) { + final Logger logger = Logger.getLogger(SurrogateQueryRegistry.class); + logger.error("Surrogate query registration failed", e); + } + } +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/ExtensionBasedSystemDefaultBackendLoader.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/ExtensionBasedSystemDefaultBackendLoader.java new file mode 100644 index 00000000..4339b70c --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/ExtensionBasedSystemDefaultBackendLoader.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Zoltan Ujhelyi, IncQuery Labs + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.internal; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.Platform; +import tools.refinery.viatra.runtime.api.ViatraQueryEngineOptions; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactory; +import tools.refinery.viatra.runtime.matchers.backend.IQueryBackendFactoryProvider; +import tools.refinery.viatra.runtime.util.ViatraQueryLoggingUtil; + +/** + * @since 2.0 + */ +public class ExtensionBasedSystemDefaultBackendLoader { + + private static final String EXTENSION_ID = "tools.refinery.viatra.runtime.querybackend"; + private static final ExtensionBasedSystemDefaultBackendLoader INSTANCE = new ExtensionBasedSystemDefaultBackendLoader(); + + public static ExtensionBasedSystemDefaultBackendLoader instance() { + return INSTANCE; + } + + public void loadKnownBackends() { + IQueryBackendFactory defaultBackend = null; + IQueryBackendFactory defaultCachingBackend = null; + IQueryBackendFactory defaultSearchBackend = null; + final IConfigurationElement[] config = Platform.getExtensionRegistry().getConfigurationElementsFor(EXTENSION_ID); + for (IConfigurationElement e : config) { + try { + IQueryBackendFactoryProvider provider = (IQueryBackendFactoryProvider) e + .createExecutableExtension("provider"); + if (provider.isSystemDefaultEngine()) { + defaultBackend = provider.getFactory(); + } + if (provider.isSystemDefaultCachingBackend()) { + defaultCachingBackend = provider.getFactory(); + } + if (provider.isSystemDefaultSearchBackend()) { + defaultSearchBackend = provider.getFactory(); + } + + } catch (CoreException ex) { + // In case errors try to continue with the next one + ViatraQueryLoggingUtil.getLogger(getClass()).error( + String.format("Error while initializing backend %s from plugin %s.", + e.getAttribute("backend"), e.getContributor().getName()), ex); + } + } + ViatraQueryEngineOptions.setSystemDefaultBackends(defaultBackend, defaultCachingBackend, defaultSearchBackend); + } + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/apiimpl/EngineContextFactory.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/apiimpl/EngineContextFactory.java new file mode 100644 index 00000000..bed07ebf --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/apiimpl/EngineContextFactory.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.internal.apiimpl; + +import org.apache.log4j.Logger; +import tools.refinery.viatra.runtime.api.ViatraQueryEngine; +import tools.refinery.viatra.runtime.api.scope.IEngineContext; +import tools.refinery.viatra.runtime.api.scope.IIndexingErrorListener; + +/** + * Internal interface for a Scope to reveal model contents to the engine. + * + * @author Bergmann Gabor + * + */ +public abstract class EngineContextFactory { + protected abstract IEngineContext createEngineContext(ViatraQueryEngine engine, IIndexingErrorListener errorListener, Logger logger); +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/apiimpl/QueryResultWrapper.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/apiimpl/QueryResultWrapper.java new file mode 100644 index 00000000..47f3268d --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/apiimpl/QueryResultWrapper.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.internal.apiimpl; + +import tools.refinery.viatra.runtime.api.ViatraQueryEngine; +import tools.refinery.viatra.runtime.matchers.backend.IMatcherCapability; +import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider; + +/** + * Internal class for wrapping a query result providing backend. It's only supported usage is by the + * {@link ViatraQueryEngineImpl} class. + *

    + * + * Important note: this class must not introduce any public method, as it will be visible through + * BaseMatcher as an API, although this class is not an API itself. + * + * @author Bergmann Gabor + * + */ +public abstract class QueryResultWrapper { + + protected IQueryResultProvider backend; + + protected abstract void setBackend(ViatraQueryEngine engine, IQueryResultProvider resultProvider, IMatcherCapability capabilities); + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/apiimpl/ViatraQueryEngineImpl.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/apiimpl/ViatraQueryEngineImpl.java new file mode 100644 index 00000000..c2341273 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/apiimpl/ViatraQueryEngineImpl.java @@ -0,0 +1,705 @@ +/******************************************************************************* + * Copyright (c) 2004-2010 Gabor Bergmann and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.internal.apiimpl; + +import org.apache.log4j.Logger; +import tools.refinery.viatra.runtime.api.*; +import tools.refinery.viatra.runtime.api.impl.BaseMatcher; +import tools.refinery.viatra.runtime.api.scope.IBaseIndex; +import tools.refinery.viatra.runtime.api.scope.IEngineContext; +import tools.refinery.viatra.runtime.api.scope.IIndexingErrorListener; +import tools.refinery.viatra.runtime.api.scope.QueryScope; +import tools.refinery.viatra.runtime.exception.ViatraQueryException; +import tools.refinery.viatra.runtime.internal.engine.LifecycleProvider; +import tools.refinery.viatra.runtime.internal.engine.ModelUpdateProvider; +import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException; +import tools.refinery.viatra.runtime.matchers.backend.*; +import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext; +import tools.refinery.viatra.runtime.matchers.context.IQueryCacheContext; +import tools.refinery.viatra.runtime.matchers.context.IQueryResultProviderAccess; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.planning.QueryProcessingException; +import tools.refinery.viatra.runtime.matchers.psystem.analysis.QueryAnalyzer; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQueries; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery; +import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery.PQueryStatus; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType; +import tools.refinery.viatra.runtime.matchers.util.IMultiLookup; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; +import tools.refinery.viatra.runtime.registry.IDefaultRegistryView; +import tools.refinery.viatra.runtime.registry.IQuerySpecificationRegistry; +import tools.refinery.viatra.runtime.registry.QuerySpecificationRegistry; +import tools.refinery.viatra.runtime.util.ViatraQueryLoggingUtil; + +import java.lang.ref.WeakReference; +import java.lang.reflect.InvocationTargetException; +import java.util.*; +import java.util.concurrent.Callable; +import java.util.stream.Collectors; + +import static tools.refinery.viatra.runtime.matchers.util.Preconditions.checkArgument; + +/** + * A VIATRA Query engine back-end (implementation) + * + * @author Bergmann Gábor + */ +public final class ViatraQueryEngineImpl extends AdvancedViatraQueryEngine + implements IQueryBackendHintProvider, IQueryCacheContext, IQueryResultProviderAccess { + + /** + * + */ + private static final String ERROR_ACCESSING_BACKEND = "Error while accessing query evaluator backend"; + /** + * + */ + private static final String QUERY_ON_DISPOSED_ENGINE_MESSAGE = "Cannot evaluate query on disposed engine!"; + /** + * The engine manager responsible for this engine. Null if this engine is unmanaged. + */ + private final ViatraQueryEngineManager manager; + /** + * The model to which the engine is attached. + */ + private final QueryScope scope; + + /** + * The context of the engine, provided by the scope. + */ + private IEngineContext engineContext; + + /** + * Initialized matchers for each query + */ + private final IMultiLookup>, ViatraQueryMatcher> matchers = + CollectionsFactory.createMultiLookup(Object.class, MemoryType.SETS, Object.class); + + /** + * The RETE and other pattern matcher implementations of the VIATRA Query Engine. + */ + private volatile Map queryBackends = new HashMap<>(); + + /** + * The current engine default hints + */ + private final ViatraQueryEngineOptions engineOptions; + + /** + * Common query analysis provided to backends + */ + private QueryAnalyzer queryAnalyzer; + + /** + * true if message delivery is currently delayed, false otherwise + */ + private boolean delayMessageDelivery = true; + + private final LifecycleProvider lifecycleProvider; + private final ModelUpdateProvider modelUpdateProvider; + private Logger logger; + private boolean disposed = false; + + /** + * @param manager + * null if unmanaged + * @param scope + * @param engineDefaultHint + * @since 1.4 + */ + public ViatraQueryEngineImpl(ViatraQueryEngineManager manager, QueryScope scope, + ViatraQueryEngineOptions engineOptions) { + super(); + this.manager = manager; + this.scope = scope; + this.lifecycleProvider = new LifecycleProvider(this, getLogger()); + this.modelUpdateProvider = new ModelUpdateProvider(this, getLogger()); + this.engineContext = scope.createEngineContext(this, taintListener, getLogger()); + + if (engineOptions != null) { + this.engineOptions = engineOptions; + } else { + this.engineOptions = ViatraQueryEngineOptions.getDefault(); + } + + } + + /** + * @param manager + * null if unmanaged + * @param scope + * @param engineDefaultHint + */ + public ViatraQueryEngineImpl(ViatraQueryEngineManager manager, QueryScope scope) { + this(manager, scope, ViatraQueryEngineOptions.getDefault()); + } + + @Override + public boolean isUpdatePropagationDelayed() { + return this.delayMessageDelivery; + } + + @Override + public V delayUpdatePropagation(Callable callable) throws InvocationTargetException { + if (!delayMessageDelivery) { + throw new IllegalStateException("Trying to delay propagation while changes are being flushed"); + } + try { + return callable.call(); + } catch (Exception e) { + throw new InvocationTargetException(e); + } + } + + @Override + public void flushChanges() { + if (!delayMessageDelivery) { + throw new IllegalStateException("Trying to flush changes while changes are already being flushed"); + } + delayMessageDelivery = false; + try { + for (IQueryBackend backend : this.queryBackends.values()) { + backend.flushUpdates(); + } + } finally { + delayMessageDelivery = true; + } + } + + @Override + public Set> getCurrentMatchers() { + return matchers.distinctValuesStream().collect(Collectors.toSet()); + } + + @Override + public > Matcher getMatcher( + IQuerySpecification querySpecification) { + return getMatcher(querySpecification, null); + } + + @Override + public > Matcher getMatcher( + IQuerySpecification querySpecification, QueryEvaluationHint optionalEvaluationHints) { + IMatcherCapability capability = getRequestedCapability(querySpecification, optionalEvaluationHints); + Matcher matcher = doGetExistingMatcher(querySpecification, capability); + if (matcher != null) { + return matcher; + } + matcher = querySpecification.instantiate(); + + BaseMatcher baseMatcher = (BaseMatcher) matcher; + ((QueryResultWrapper) baseMatcher).setBackend(this, + getResultProvider(querySpecification, optionalEvaluationHints), capability); + internalRegisterMatcher(querySpecification, baseMatcher); + return matcher; + } + + @Override + public > Matcher getExistingMatcher( + IQuerySpecification querySpecification) { + return getExistingMatcher(querySpecification, null); + } + + @Override + public > Matcher getExistingMatcher( + IQuerySpecification querySpecification, QueryEvaluationHint optionalOverrideHints) { + return doGetExistingMatcher(querySpecification, getRequestedCapability(querySpecification, optionalOverrideHints)); + } + + @SuppressWarnings("unchecked") + private > Matcher doGetExistingMatcher( + IQuerySpecification querySpecification, IMatcherCapability requestedCapability) { + for (ViatraQueryMatcher matcher : matchers.lookupOrEmpty(querySpecification)) { + BaseMatcher baseMatcher = (BaseMatcher) matcher; + if (baseMatcher.getCapabilities().canBeSubstitute(requestedCapability)) + return (Matcher) matcher; + } + return null; + } + + @Override + public ViatraQueryMatcher getMatcher(String patternFQN) { + IQuerySpecificationRegistry registry = QuerySpecificationRegistry.getInstance(); + IDefaultRegistryView view = registry.getDefaultView(); + IQuerySpecification> querySpecification = view + .getEntry(patternFQN).get(); + if (querySpecification != null) { + return getMatcher(querySpecification); + } else { + throw new ViatraQueryException(String.format( + "No matcher could be constructed for the pattern with FQN %s; if the generated matcher class is not available, please access for the first time using getMatcher(IQuerySpecification)", + patternFQN), "No matcher could be constructed for given pattern FQN."); + } + } + + @Override + public IBaseIndex getBaseIndex() { + return engineContext.getBaseIndex(); + } + + public final Logger getLogger() { + if (logger == null) { + final int hash = System.identityHashCode(this); + logger = Logger.getLogger(ViatraQueryLoggingUtil.getLogger(ViatraQueryEngine.class).getName() + "." + hash); + if (logger == null) + throw new AssertionError( + "Configuration error: unable to create VIATRA Query runtime logger for engine " + hash); + } + return logger; + } + + ///////////////// internal stuff ////////////// + private void internalRegisterMatcher(IQuerySpecification querySpecification, ViatraQueryMatcher matcher) { + matchers.addPair(querySpecification, matcher); + lifecycleProvider.matcherInstantiated(matcher); + } + + /** + * Provides access to the selected query backend component of the VIATRA Query Engine. + */ + @Override + public IQueryBackend getQueryBackend(IQueryBackendFactory iQueryBackendFactory) { + IQueryBackend iQueryBackend = queryBackends.get(iQueryBackendFactory); + if (iQueryBackend == null) { + // do this first, to make sure the runtime context exists + final IQueryRuntimeContext queryRuntimeContext = engineContext.getQueryRuntimeContext(); + + // maybe the backend has been created in the meantime when the indexer was initialized and queried for + // derived features + // no need to instantiate a new backend in that case + iQueryBackend = queryBackends.get(iQueryBackendFactory); + if (iQueryBackend == null) { + + // need to instantiate the backend + iQueryBackend = iQueryBackendFactory.create(new IQueryBackendContext() { + + @Override + public IQueryRuntimeContext getRuntimeContext() { + return queryRuntimeContext; + } + + @Override + public IQueryCacheContext getQueryCacheContext() { + return ViatraQueryEngineImpl.this; + } + + @Override + public Logger getLogger() { + return logger; + } + + @Override + public IQueryBackendHintProvider getHintProvider() { + return ViatraQueryEngineImpl.this; + } + + @Override + public IQueryResultProviderAccess getResultProviderAccess() { + return ViatraQueryEngineImpl.this; + } + + @Override + public QueryAnalyzer getQueryAnalyzer() { + if (queryAnalyzer == null) + queryAnalyzer = new QueryAnalyzer(queryRuntimeContext.getMetaContext()); + return queryAnalyzer; + } + + @Override + public boolean areUpdatesDelayed() { + return ViatraQueryEngineImpl.this.delayMessageDelivery; + } + + @Override + public IMatcherCapability getRequiredMatcherCapability(PQuery query, + QueryEvaluationHint hint) { + return engineOptions.getQueryBackendFactory(hint).calculateRequiredCapability(query, hint); + } + + + + }); + queryBackends.put(iQueryBackendFactory, iQueryBackend); + } + } + return iQueryBackend; + } + + ///////////////// advanced stuff ///////////// + + @Override + public void dispose() { + if (manager != null) { + throw new UnsupportedOperationException( + String.format("Cannot dispose() managed VIATRA Query Engine. Attempted for scope %s.", scope)); + } + wipe(); + + this.disposed = true; + + // called before base index disposal to allow removal of base listeners + lifecycleProvider.engineDisposed(); + + try { + engineContext.dispose(); + } catch (IllegalStateException ex) { + getLogger().warn( + "The base index could not be disposed along with the VIATRA Query engine, as there are still active listeners on it."); + } + } + + @Override + public void wipe() { + if (manager != null) { + throw new UnsupportedOperationException( + String.format("Cannot wipe() managed VIATRA Query Engine. Attempted for scope %s.", scope)); + } + if (queryBackends != null) { + for (IQueryBackend backend : queryBackends.values()) { + backend.dispose(); + } + queryBackends.clear(); + } + matchers.clear(); + queryAnalyzer = null; + lifecycleProvider.engineWiped(); + } + + /** + * Indicates whether the engine is in a tainted, inconsistent state. + */ + private boolean tainted = false; + private IIndexingErrorListener taintListener = new SelfTaintListener(this); + + private static class SelfTaintListener implements IIndexingErrorListener { + WeakReference queryEngineRef; + + public SelfTaintListener(ViatraQueryEngineImpl queryEngine) { + this.queryEngineRef = new WeakReference(queryEngine); + } + + public void engineBecameTainted(String description, Throwable t) { + final ViatraQueryEngineImpl queryEngine = queryEngineRef.get(); + if (queryEngine != null) { + queryEngine.tainted = true; + queryEngine.lifecycleProvider.engineBecameTainted(description, t); + } + } + + private boolean noTaintDetectedYet = true; + + protected void notifyTainted(String description, Throwable t) { + if (noTaintDetectedYet) { + noTaintDetectedYet = false; + engineBecameTainted(description, t); + } + } + + @Override + public void error(String description, Throwable t) { + // Errors does not mean tainting + } + + @Override + public void fatal(String description, Throwable t) { + notifyTainted(description, t); + } + } + + @Override + public boolean isTainted() { + return tainted; + } + + @Override + public boolean isManaged() { + return manager != null; + // return isAdvanced; ??? + } + + private IQueryResultProvider getUnderlyingResultProvider( + final BaseMatcher matcher) { + // IQueryResultProvider resultProvider = reteEngine.accessMatcher(matcher.getSpecification()); + return matcher.backend; + } + + @Override + public void addMatchUpdateListener(final ViatraQueryMatcher matcher, + final IMatchUpdateListener listener, boolean fireNow) { + + checkArgument(listener != null, "Cannot add null listener!"); + checkArgument(matcher.getEngine() == this, "Cannot register listener for matcher of different engine!"); + checkArgument(!disposed, "Cannot register listener on matcher of disposed engine!"); + + final BaseMatcher bm = (BaseMatcher) matcher; + + final IUpdateable updateDispatcher = (updateElement, isInsertion) -> { + Match match = null; + try { + match = bm.newMatch(updateElement.getElements()); + if (isInsertion) + listener.notifyAppearance(match); + else + listener.notifyDisappearance(match); + } catch (Throwable e) { // NOPMD + if (e instanceof Error) + throw (Error) e; + logger.warn( + String.format( + "The incremental pattern matcher encountered an error during %s a callback on %s of match %s of pattern %s. Error message: %s. (Developer note: %s in %s callback)", + match == null ? "preparing" : "invoking", isInsertion ? "insertion" : "removal", + match == null ? updateElement.toString() : match.prettyPrint(), + matcher.getPatternName(), e.getMessage(), e.getClass().getSimpleName(), listener), + e); + } + + }; + + IQueryResultProvider resultProvider = getUnderlyingResultProvider(bm); + resultProvider.addUpdateListener(updateDispatcher, listener, fireNow); + } + + @Override + public void removeMatchUpdateListener(ViatraQueryMatcher matcher, + IMatchUpdateListener listener) { + checkArgument(listener != null, "Cannot remove null listener!"); + checkArgument(matcher.getEngine() == this, "Cannot remove listener from matcher of different engine!"); + checkArgument(!disposed, "Cannot remove listener from matcher of disposed engine!"); + + final BaseMatcher bm = (BaseMatcher) matcher; + + try { + IQueryResultProvider resultProvider = getUnderlyingResultProvider(bm); + resultProvider.removeUpdateListener(listener); + } catch (Exception e) { + logger.error( + "Error while removing listener " + listener + " from the matcher of " + matcher.getPatternName(), + e); + } + } + + @Override + public void addModelUpdateListener(ViatraQueryModelUpdateListener listener) { + modelUpdateProvider.addListener(listener); + } + + @Override + public void removeModelUpdateListener(ViatraQueryModelUpdateListener listener) { + modelUpdateProvider.removeListener(listener); + } + + @Override + public void addLifecycleListener(ViatraQueryEngineLifecycleListener listener) { + lifecycleProvider.addListener(listener); + } + + @Override + public void removeLifecycleListener(ViatraQueryEngineLifecycleListener listener) { + lifecycleProvider.removeListener(listener); + } + + /** + * Returns an internal interface towards the query backend to feed the matcher with results. + * + * @param query + * the pattern for which the result provider should be delivered + * + * @throws ViatraQueryRuntimeException + */ + public IQueryResultProvider getResultProvider(IQuerySpecification query) { + Preconditions.checkState(!disposed, QUERY_ON_DISPOSED_ENGINE_MESSAGE); + + return getResultProviderInternal(query, null); + } + + /** + * Returns an internal interface towards the query backend to feed the matcher with results. + * + * @param query + * the pattern for which the result provider should be delivered + * + * @throws ViatraQueryRuntimeException + */ + public IQueryResultProvider getResultProvider(IQuerySpecification query, QueryEvaluationHint hint) { + Preconditions.checkState(!disposed, QUERY_ON_DISPOSED_ENGINE_MESSAGE); + + return getResultProviderInternal(query, hint); + } + + /** + * This method returns the result provider exactly as described by the passed hint. Query cannot be null! Use + * {@link #getQueryEvaluationHint(IQuerySpecification, QueryEvaluationHint)} before passing a hint to this method to + * make sure engine and query specific hints are correctly applied. + * + * @throws ViatraQueryRuntimeException + */ + private IQueryResultProvider getResultProviderInternal(IQuerySpecification query, QueryEvaluationHint hint) { + return getResultProviderInternal(query.getInternalQueryRepresentation(), hint); + } + + /** + * This method returns the result provider exactly as described by the passed hint. Query cannot be null! Use + * {@link #getQueryEvaluationHint(IQuerySpecification, QueryEvaluationHint)} before passing a hint to this method to + * make sure engine and query specific hints are correctly applied. + * + * @throws ViatraQueryRuntimeException + */ + private IQueryResultProvider getResultProviderInternal(PQuery query, QueryEvaluationHint hint) { + Preconditions.checkArgument(query != null, "Query cannot be null!"); + Preconditions.checkArgument(query.getStatus() != PQueryStatus.ERROR, "Cannot initialize a result provider for the erronoues query `%s`.", query.getSimpleName()); + final IQueryBackend backend = getQueryBackend(engineOptions.getQueryBackendFactory(getQueryEvaluationHint(query, hint))); + return backend.getResultProvider(query, hint); + } + + /** + * Returns the query backend (influenced by the hint system), even if it is a non-caching backend. + * + * @throws ViatraQueryRuntimeException + */ + private IQueryBackend getQueryBackend(PQuery query) { + final IQueryBackendFactory factory = engineOptions.getQueryBackendFactory(getQueryEvaluationHint(query)); + return getQueryBackend(factory); + } + + /** + * Returns a caching query backend (influenced by the hint system). + * + * @throws ViatraQueryRuntimeException + */ + private IQueryBackend getCachingQueryBackend(PQuery query) { + IQueryBackend regularBackend = getQueryBackend(query); + if (regularBackend.isCaching()) + return regularBackend; + else + return getQueryBackend(engineOptions.getDefaultCachingBackendFactory()); + } + + @Override + public boolean isResultCached(PQuery query) { + try { + return null != getCachingQueryBackend(query).peekExistingResultProvider(query); + } catch (ViatraQueryException iqe) { + getLogger().error(ERROR_ACCESSING_BACKEND, iqe); + return false; + } + } + + @Override + public IQueryResultProvider getCachingResultProvider(PQuery query) { + try { + return getCachingQueryBackend(query).getResultProvider(query); + } catch (ViatraQueryException iqe) { + getLogger().error(ERROR_ACCESSING_BACKEND, iqe); + throw iqe; + } + } + + private QueryEvaluationHint getEngineDefaultHint() { + return engineOptions.getEngineDefaultHints(); + } + + @Override + public QueryEvaluationHint getQueryEvaluationHint(PQuery query) { + return getEngineDefaultHint().overrideBy(query.getEvaluationHints()); + } + + private QueryEvaluationHint getQueryEvaluationHint(IQuerySpecification querySpecification, + QueryEvaluationHint optionalOverrideHints) { + return getQueryEvaluationHint(querySpecification.getInternalQueryRepresentation()) + .overrideBy(optionalOverrideHints); + } + + private QueryEvaluationHint getQueryEvaluationHint(PQuery query, QueryEvaluationHint optionalOverrideHints) { + return getQueryEvaluationHint(query).overrideBy(optionalOverrideHints); + } + + private IMatcherCapability getRequestedCapability(IQuerySpecification querySpecification, + QueryEvaluationHint optionalOverrideHints) { + final QueryEvaluationHint hint = getQueryEvaluationHint(querySpecification, optionalOverrideHints); + return engineOptions.getQueryBackendFactory(hint) + .calculateRequiredCapability(querySpecification.getInternalQueryRepresentation(), hint); + } + + @Override + public void prepareGroup(IQueryGroup queryGroup, final QueryEvaluationHint optionalEvaluationHints) { + try { + Preconditions.checkState(!disposed, QUERY_ON_DISPOSED_ENGINE_MESSAGE); + + final Set> specifications = new HashSet>( + queryGroup.getSpecifications()); + final Collection patterns = specifications.stream().map( + IQuerySpecification::getInternalQueryRepresentation).collect(Collectors.toList()); + patterns.forEach(PQuery::ensureInitialized); + + Collection erroneousPatterns = patterns.stream(). + filter(PQueries.queryStatusPredicate(PQueryStatus.ERROR)). + map(PQuery::getFullyQualifiedName). + collect(Collectors.toList()); + Preconditions.checkState(erroneousPatterns.isEmpty(), "Erroneous query(s) found: %s", + erroneousPatterns.stream().collect(Collectors.joining(", "))); + + // TODO maybe do some smarter preparation per backend? + try { + engineContext.getBaseIndex().coalesceTraversals(new Callable() { + @Override + public Void call() throws Exception { + for (IQuerySpecification query : specifications) { + getResultProviderInternal(query, optionalEvaluationHints); + } + return null; + } + }); + } catch (InvocationTargetException ex) { + final Throwable cause = ex.getCause(); + if (cause instanceof QueryProcessingException) + throw (QueryProcessingException) cause; + if (cause instanceof ViatraQueryException) + throw (ViatraQueryException) cause; + if (cause instanceof RuntimeException) + throw (RuntimeException) cause; + assert (false); + } + } catch (QueryProcessingException e) { + throw new ViatraQueryException(e); + } + } + + @Override + public QueryScope getScope() { + return scope; + } + + @Override + public ViatraQueryEngineOptions getEngineOptions() { + return engineOptions; + } + + @Override + public IQueryResultProvider getResultProviderOfMatcher(ViatraQueryMatcher matcher) { + return ((QueryResultWrapper) matcher).backend; + } + + @Override + public IQueryResultProvider getResultProvider(PQuery query, QueryEvaluationHint overrideHints) { + try { + return getResultProviderInternal(query, overrideHints); + } catch (ViatraQueryException e) { + getLogger().error(ERROR_ACCESSING_BACKEND, e); + throw e; + } + } + + @Override + public boolean isDisposed() { + return disposed; + } + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/engine/LifecycleProvider.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/engine/LifecycleProvider.java new file mode 100644 index 00000000..8bbf2a66 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/engine/LifecycleProvider.java @@ -0,0 +1,138 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Abel Hegedus, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.internal.engine; + +import java.util.ArrayList; + +import org.apache.log4j.Logger; +import tools.refinery.viatra.runtime.api.AdvancedViatraQueryEngine; +import tools.refinery.viatra.runtime.api.IPatternMatch; +import tools.refinery.viatra.runtime.api.ViatraQueryEngineLifecycleListener; +import tools.refinery.viatra.runtime.api.ViatraQueryMatcher; + +public final class LifecycleProvider extends ListenerContainer implements ViatraQueryEngineLifecycleListener{ + + private final Logger logger; + + /** + * @param queryEngine + */ + public LifecycleProvider(AdvancedViatraQueryEngine queryEngine, Logger logger) { + this.logger = logger; + } + + @Override + protected void listenerAdded(ViatraQueryEngineLifecycleListener listener) { + logger.debug("Lifecycle listener " + listener + " added to engine."); + } + + @Override + protected void listenerRemoved(ViatraQueryEngineLifecycleListener listener) { + logger.debug("Lifecycle listener " + listener + " removed from engine."); + } + +// public void propagateEventToListeners(Predicate function) { +// if (!listeners.isEmpty()) { +// for (ViatraQueryEngineLifecycleListener listener : new ArrayList(listeners)) { +// try { +// function.apply(listener); +// } catch (Exception ex) { +// logger.error( +// "VIATRA Query encountered an error in delivering notification to listener " +// + listener + ".", ex); +// } +// } +// } +// } + + @Override + public void matcherInstantiated(final ViatraQueryMatcher matcher) { + if (!listeners.isEmpty()) { + for (ViatraQueryEngineLifecycleListener listener : new ArrayList(listeners)) { + try { + listener.matcherInstantiated(matcher); + } catch (Exception ex) { + logger.error( + "VIATRA Query encountered an error in delivering matcher initialization notification to listener " + + listener + ".", ex); + } + } + } +// propagateEventToListeners(new Predicate() { +// public boolean apply(ViatraQueryEngineLifecycleListener listener) { +// listener.matcherInstantiated(matcher); +// return true; +// } +// }); + } + + @Override + public void engineBecameTainted(String description, Throwable t) { + if (!listeners.isEmpty()) { + for (ViatraQueryEngineLifecycleListener listener : new ArrayList(listeners)) { + try { + listener.engineBecameTainted(description, t); + } catch (Exception ex) { + logger.error( + "VIATRA Query encountered an error in delivering engine tainted notification to listener " + + listener + ".", ex); + } + } + } +// propagateEventToListeners(new Predicate() { +// public boolean apply(ViatraQueryEngineLifecycleListener listener) { +// listener.engineBecameTainted(); +// return true; +// } +// }); + } + + @Override + public void engineWiped() { + if (!listeners.isEmpty()) { + for (ViatraQueryEngineLifecycleListener listener : new ArrayList(listeners)) { + try { + listener.engineWiped(); + } catch (Exception ex) { + logger.error( + "VIATRA Query encountered an error in delivering engine wiped notification to listener " + + listener + ".", ex); + } + } + } +// propagateEventToListeners(new Predicate() { +// public boolean apply(ViatraQueryEngineLifecycleListener listener) { +// listener.engineWiped(); +// return true; +// } +// }); + } + + @Override + public void engineDisposed() { + if (!listeners.isEmpty()) { + for (ViatraQueryEngineLifecycleListener listener : new ArrayList(listeners)) { + try { + listener.engineDisposed(); + } catch (Exception ex) { + logger.error( + "VIATRA Query encountered an error in delivering engine disposed notification to listener " + + listener + ".", ex); + } + } + } +// propagateEventToListeners(new Predicate() { +// public boolean apply(ViatraQueryEngineLifecycleListener listener) { +// listener.engineDisposed(); +// return true; +// } +// }); + } + + } \ No newline at end of file diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/engine/ListenerContainer.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/engine/ListenerContainer.java new file mode 100644 index 00000000..34133bed --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/engine/ListenerContainer.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Abel Hegedus, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.internal.engine; + +import static tools.refinery.viatra.runtime.matchers.util.Preconditions.checkArgument; + +import java.util.HashSet; +import java.util.Set; + +public abstract class ListenerContainer { + + protected final Set listeners; + + public ListenerContainer() { + this.listeners = new HashSet(); + } + + public synchronized void addListener(Listener listener) { + checkArgument(listener != null, "Cannot add null listener!"); + boolean added = listeners.add(listener); + if(added) { + listenerAdded(listener); + } + } + + public synchronized void removeListener(Listener listener) { + checkArgument(listener != null, "Cannot remove null listener!"); + boolean removed = listeners.remove(listener); + if(removed) { + listenerRemoved(listener); + } + } + + protected abstract void listenerAdded(Listener listener); + + protected abstract void listenerRemoved(Listener listener); +} \ No newline at end of file diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/engine/ModelUpdateProvider.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/engine/ModelUpdateProvider.java new file mode 100644 index 00000000..1f2c27e8 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/internal/engine/ModelUpdateProvider.java @@ -0,0 +1,214 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Abel Hegedus, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.internal.engine; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.log4j.Logger; +import tools.refinery.viatra.runtime.api.AdvancedViatraQueryEngine; +import tools.refinery.viatra.runtime.api.IMatchUpdateListener; +import tools.refinery.viatra.runtime.api.IPatternMatch; +import tools.refinery.viatra.runtime.api.ViatraQueryEngineLifecycleListener; +import tools.refinery.viatra.runtime.api.ViatraQueryMatcher; +import tools.refinery.viatra.runtime.api.ViatraQueryModelUpdateListener; +import tools.refinery.viatra.runtime.api.ViatraQueryModelUpdateListener.ChangeLevel; +import tools.refinery.viatra.runtime.api.scope.ViatraBaseIndexChangeListener; +import tools.refinery.viatra.runtime.exception.ViatraQueryException; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; + +public final class ModelUpdateProvider extends ListenerContainer { + + private final AdvancedViatraQueryEngine queryEngine; + private ChangeLevel currentChange = ChangeLevel.NO_CHANGE; + private ChangeLevel maxLevel = ChangeLevel.NO_CHANGE; + private final Map> listenerMap; + private final Logger logger; + + public ModelUpdateProvider(AdvancedViatraQueryEngine queryEngine, Logger logger) { + super(); + this.queryEngine = queryEngine; + this.logger = logger; + listenerMap = new EnumMap<>(ChangeLevel.class); + } + + @Override + protected void listenerAdded(ViatraQueryModelUpdateListener listener) { + // check ChangeLevel + // create callback for given level if required + if(listenerMap.isEmpty()) { + try { + this.queryEngine.getBaseIndex().addBaseIndexChangeListener(indexListener); + // add listener to new matchers (use lifecycle listener) + this.queryEngine.addLifecycleListener(selfListener); + } catch (ViatraQueryException e) { + throw new IllegalStateException("Model update listener used on engine without base index", e); + } + } + + ChangeLevel changeLevel = listener.getLevel(); + listenerMap.computeIfAbsent(changeLevel, k -> CollectionsFactory.createSet()).add(listener); + // increase or keep max level of listeners + ChangeLevel oldMaxLevel = maxLevel; + maxLevel = maxLevel.changeOccured(changeLevel); + if(!maxLevel.equals(oldMaxLevel) && ChangeLevel.MATCHSET.compareTo(oldMaxLevel) > 0 && ChangeLevel.MATCHSET.compareTo(maxLevel) <= 0) { + // add matchUpdateListener to all matchers + for (ViatraQueryMatcher matcher : this.queryEngine.getCurrentMatchers()) { + this.queryEngine.addMatchUpdateListener(matcher, matchSetListener, false); + } + } + } + + @Override + protected void listenerRemoved(ViatraQueryModelUpdateListener listener) { + ChangeLevel changeLevel = listener.getLevel(); + Collection old = listenerMap.getOrDefault(changeLevel, Collections.emptySet()); + boolean removed = old.remove(listener); + if(removed) { + if (old.isEmpty()) listenerMap.remove(changeLevel); + } else { + handleUnsuccesfulRemove(listener); + } + + updateMaxLevel(); + + if(listenerMap.isEmpty()) { + this.queryEngine.removeLifecycleListener(selfListener); + removeBaseIndexChangeListener(); + } + } + + private void removeBaseIndexChangeListener() { + try { + this.queryEngine.getBaseIndex().removeBaseIndexChangeListener(indexListener); + } catch (ViatraQueryException e) { + throw new IllegalStateException("Model update listener used on engine without base index", e); + } + } + + private void updateMaxLevel() { + if(!listenerMap.containsKey(maxLevel)) { + ChangeLevel newMaxLevel = ChangeLevel.NO_CHANGE; + for (ChangeLevel level : new HashSet<>(listenerMap.keySet())) { + newMaxLevel = newMaxLevel.changeOccured(level); + } + maxLevel = newMaxLevel; + } + if(maxLevel.compareTo(ChangeLevel.MATCHSET) < 0) { + // remove listener from matchers + for (ViatraQueryMatcher matcher : this.queryEngine.getCurrentMatchers()) { + this.queryEngine.removeMatchUpdateListener(matcher, matchSetListener); + } + } + } + + private void handleUnsuccesfulRemove(ViatraQueryModelUpdateListener listener) { + for (Entry> entry : listenerMap.entrySet()) { + Collection existingListeners = entry.getValue(); + // if the listener is contained in some other bucket, remove it from there + if(existingListeners.remove(listener)) { + logger.error("Listener "+listener+" change level changed since initialization!"); + if (existingListeners.isEmpty()) listenerMap.remove(entry.getKey()); + return; // listener is contained only once + } + } + logger.error("Listener "+listener+" already removed from map (e.g. engine was already disposed)!"); + } + + private void notifyListeners() { + + // any change that occurs after this point should be regarded as a new event + // FIXME what should happen when a listener creates new notifications? + // -> other listeners will get events in different order + ChangeLevel tempLevel = currentChange; + currentChange = ChangeLevel.NO_CHANGE; + + if(!listenerMap.isEmpty()) { + for (ChangeLevel level : new HashSet<>(listenerMap.keySet())) { + if(tempLevel.compareTo(level) >= 0) { + for (ViatraQueryModelUpdateListener listener : new ArrayList<>(listenerMap.get(level))) { + try { + listener.notifyChanged(tempLevel); + } catch (Exception ex) { + logger.error( + "VIATRA Query encountered an error in delivering model update notification to listener " + + listener + ".", ex); + } + } + } + } + } else { + throw new IllegalStateException("Notify listeners must not be called without listeners! Maybe an update callback was not removed correctly."); + } + + } + + // model update "providers": + // - model: IQBase callback even if not dirty + // - index: IQBase dirty callback + private final ViatraBaseIndexChangeListener indexListener = new ViatraBaseIndexChangeListener() { + + @Override + public boolean onlyOnIndexChange() { + return false; + } + + @Override + public void notifyChanged(boolean indexChanged) { + if(indexChanged) { + currentChange = currentChange.changeOccured(ChangeLevel.INDEX); + } else { + currentChange = currentChange.changeOccured(ChangeLevel.MODEL); + } + notifyListeners(); + } + + }; + // - matchset: add the same listener to each matcher and use a dirty flag. needs IQBase callback as well + private final IMatchUpdateListener matchSetListener = new IMatchUpdateListener() { + + @Override + public void notifyDisappearance(IPatternMatch match) { + currentChange = currentChange.changeOccured(ChangeLevel.MATCHSET); + } + + @Override + public void notifyAppearance(IPatternMatch match) { + currentChange = currentChange.changeOccured(ChangeLevel.MATCHSET); + } + }; + + private final ViatraQueryEngineLifecycleListener selfListener = new ViatraQueryEngineLifecycleListener() { + + @Override + public void matcherInstantiated(ViatraQueryMatcher matcher) { + if (maxLevel.compareTo(ChangeLevel.MATCHSET) >= 0) { + ModelUpdateProvider.this.queryEngine.addMatchUpdateListener(matcher, matchSetListener, false); + } + } + + @Override + public void engineWiped() {} + + @Override + public void engineDisposed() { + removeBaseIndexChangeListener(); + listenerMap.clear(); + maxLevel = ChangeLevel.NO_CHANGE; + } + + @Override + public void engineBecameTainted(String description, Throwable t) {} + }; +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/ExtensionBasedQuerySpecificationLoader.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/ExtensionBasedQuerySpecificationLoader.java new file mode 100644 index 00000000..40bf3e67 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/ExtensionBasedQuerySpecificationLoader.java @@ -0,0 +1,303 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Abel Hegedus, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.registry; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.Platform; +import tools.refinery.viatra.runtime.IExtensions; +import tools.refinery.viatra.runtime.api.IQueryGroup; +import tools.refinery.viatra.runtime.api.IQuerySpecification; +import tools.refinery.viatra.runtime.extensibility.IQueryGroupProvider; +import tools.refinery.viatra.runtime.extensibility.IQuerySpecificationProvider; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType; +import tools.refinery.viatra.runtime.matchers.util.IMemoryView; +import tools.refinery.viatra.runtime.matchers.util.IMultiLookup; +import tools.refinery.viatra.runtime.util.ViatraQueryLoggingUtil; + +/** + * Loader for the {@link QuerySpecificationRegistry} based on the query group extensions generated by the VIATRA Query + * builder. The loader has a single instance that processes the extensions on demand if the platform is running, caches + * the results and updates the {@link QuerySpecificationRegistry}. Note that the loader does not perform class loading + * on the query group if possible. + * + *

    + * The class has a single instance accessible with {@link #getInstance()}. + * + * @author Abel Hegedus + * @since 1.3 + * + */ +public class ExtensionBasedQuerySpecificationLoader { + + public static final String CONNECTOR_ID = "tools.refinery.viatra.runtime.querygroup.extension.based.connector"; + + private static final String DUPLICATE_QUERY_GROUP_MESSAGE = "Duplicate query group identifier %s for plugin %s (already contributed by %s)"; + private static final ExtensionBasedQuerySpecificationLoader INSTANCE = new ExtensionBasedQuerySpecificationLoader(); + + private IMultiLookup contributingPluginOfGroupMap = + CollectionsFactory.createMultiLookup(Object.class, MemoryType.SETS, Object.class); + private Map contributedQueryGroups; + + private ExtensionBasedSourceConnector sourceConnector; + + + /** + * @return the single instance of the loader. + */ + public static ExtensionBasedQuerySpecificationLoader getInstance() { + return INSTANCE; + } + + /** + * Loads the query specifications that are registered through extension points into the + * {@link QuerySpecificationRegistry}. + */ + public void loadRegisteredQuerySpecificationsIntoRegistry() { + ((QuerySpecificationRegistry) QuerySpecificationRegistry.getInstance()).addDelayedSourceConnector(getSourceConnector()); + } + + /** + * Return a source connector that can be used to load query specifications contributed through + * extensions into a {@link IQuerySpecificationRegistry}. + * + * @return the source connector + */ + public IRegistrySourceConnector getSourceConnector() { + if (this.sourceConnector == null) { + this.sourceConnector = new ExtensionBasedSourceConnector(); + } + return sourceConnector; + } + + private Map getRegisteredQueryGroups() { + if(contributedQueryGroups != null) { + return contributedQueryGroups; + } + contributedQueryGroups = new HashMap<>(); + if (Platform.isRunning()) { + for (IConfigurationElement e : Platform.getExtensionRegistry().getConfigurationElementsFor(IExtensions.QUERY_SPECIFICATION_EXTENSION_POINT_ID)) { + if (e.isValid()) { + processExtension(e); + } + } + } + return contributedQueryGroups; + } + + private void processExtension(IConfigurationElement el) { + String id = null; + try { + String contributorName = el.getContributor().getName(); + id = el.getAttribute("id"); + if(id == null) { + throw new IllegalStateException(String.format("Query group extension identifier is required (plug-in: %s)!", contributorName)); + } + + QueryGroupProvider provider = new QueryGroupProvider(el); + + QueryGroupProvider queryGroupInMap = contributedQueryGroups.get(id); + if(queryGroupInMap != null) { + IMemoryView contributorPlugins = contributingPluginOfGroupMap.lookupOrEmpty(id); + throw new IllegalStateException(String.format(DUPLICATE_QUERY_GROUP_MESSAGE, id, contributorName, contributorPlugins.distinctValues())); + } + + contributedQueryGroups.put(id, provider); + contributingPluginOfGroupMap.addPair(id, contributorName); + } catch (Exception e) { + // If there are serious compilation errors in the file loaded by the query registry, an error is thrown + if (id == null) { + id = "undefined in plugin.xml"; + } + ViatraQueryLoggingUtil.getLogger(ExtensionBasedQuerySpecificationLoader.class).error( + "[ExtensionBasedQuerySpecificationLoader] Exception during query specification registry initialization when preparing group: " + + id + "! " + e.getMessage(), e); + } + } + + /** + * @author Abel Hegedus + * + */ + private final class ExtensionBasedSourceConnector implements IRegistrySourceConnector { + + private Set listeners; + + public ExtensionBasedSourceConnector() { + this.listeners = new HashSet<>(); + } + + @Override + public String getIdentifier() { + return ExtensionBasedQuerySpecificationLoader.CONNECTOR_ID; + } + + @Override + public void addListener(IConnectorListener listener) { + Objects.requireNonNull(listener, "Listener must not be null!"); + boolean added = listeners.add(listener); + if(added) { + for (QueryGroupProvider queryGroupProvider : getRegisteredQueryGroups().values()) { + for (IQuerySpecificationProvider specificationProvider : queryGroupProvider.getQuerySpecificationProviders()) { + listener.querySpecificationAdded(this, specificationProvider); + } + } + } + } + + @Override + public void removeListener(IConnectorListener listener) { + Objects.requireNonNull(listener, "Listener must not be null!"); + listeners.remove(listener); + } + + @Override + public boolean includeSpecificationsInDefaultViews() { + return true; + } + } + + /** + * Provider implementation that uses the group extension to load the query group on-demand. + * It also provides the set of query FQNs that are part of the group without class loading. + * Once loaded, the query group is cached for future use. + * + * @author Abel Hegedus + */ + private static final class QueryGroupProvider implements IQueryGroupProvider { + + private static final String DUPLICATE_FQN_MESSAGE = "Duplicate FQN %s in query group extension point (plug-in %s)"; + private final IConfigurationElement element; + private IQueryGroup queryGroup; + private Set querySpecificationFQNs; + private Map querySpecificationMap; + + public QueryGroupProvider(IConfigurationElement element) { + this.element = element; + this.queryGroup = null; + this.querySpecificationFQNs = null; + this.querySpecificationMap = null; + } + + @Override + public IQueryGroup get() { + try{ + if(queryGroup == null) { + queryGroup = (IQueryGroup) element.createExecutableExtension("group"); + } + return queryGroup; + } catch (CoreException e) { + throw new IllegalStateException(e.getMessage(), e); + } + } + + @Override + public Set getQuerySpecificationFQNs() { + if(querySpecificationFQNs == null) { + Set fqns = new HashSet<>(); + for (IConfigurationElement e : element.getChildren("query-specification")) { + if (e.isValid()) { + String fqn = e.getAttribute("fqn"); + boolean added = fqns.add(fqn); + if(!added) { + String contributorName = e.getContributor().getName(); + throw new IllegalArgumentException(String.format(DUPLICATE_FQN_MESSAGE,fqn, contributorName)); + } + } + } + if(fqns.isEmpty()) { + // we must load the class and get the specifications + IQueryGroup loadedQueryGroup = get(); + for (IQuerySpecification specification : loadedQueryGroup.getSpecifications()) { + String fullyQualifiedName = specification.getFullyQualifiedName(); + boolean added = fqns.add(fullyQualifiedName); + if(!added) { + String contributorName = element.getContributor().getName(); + throw new IllegalArgumentException(String.format(DUPLICATE_FQN_MESSAGE, fullyQualifiedName, contributorName)); + } + } + } + // we will never change the set after initialization + querySpecificationFQNs = new HashSet<>(fqns); + } + return querySpecificationFQNs; + } + + @Override + public Set getQuerySpecificationProviders() { + return new HashSet<>(getQuerySpecificationMap().values()); + } + + private Map getQuerySpecificationMap() { + if(querySpecificationMap == null){ + querySpecificationMap = new HashMap<>(); + Set fqns = getQuerySpecificationFQNs(); + for (String fqn : fqns) { + querySpecificationMap.put(fqn, new GroupBasedQuerySpecificationProvider(fqn, this)); + } + } + return querySpecificationMap; + } + + } + + /** + * Provider implementation that uses the query group extension to load a query specification by its FQN. Note that + * the FQN of the provided query specification is set with the constructor and can be requested without loading the + * class. Once loaded, the query specification is cached for future use. + * + * @author Abel Hegedus + * + */ + private static final class GroupBasedQuerySpecificationProvider implements IQuerySpecificationProvider { + + private String queryFQN; + private QueryGroupProvider queryGroupProvider; + private IQuerySpecification specification; + + public GroupBasedQuerySpecificationProvider(String queryFQN, QueryGroupProvider queryGroupProvider) { + this.queryFQN = queryFQN; + this.queryGroupProvider = queryGroupProvider; + this.specification = null; + } + + @Override + public IQuerySpecification get() { + if(specification == null) { + if(queryGroupProvider.getQuerySpecificationFQNs().contains(queryFQN)) { + for (IQuerySpecification spec : queryGroupProvider.get().getSpecifications()) { + if(spec.getFullyQualifiedName().equals(queryFQN)){ + this.specification = spec; + } + } + } else { + throw new IllegalStateException(String.format("Could not find query specifition %s in group (plug-in %s)", queryFQN, queryGroupProvider.element.getContributor().getName())); + } + } + return specification; + } + + @Override + public String getFullyQualifiedName() { + return queryFQN; + } + + @Override + public String getSourceProjectName() { + return queryGroupProvider.element.getContributor().getName(); + } + } +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/IConnectorListener.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/IConnectorListener.java new file mode 100644 index 00000000..318415cc --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/IConnectorListener.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Abel Hegedus, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.registry; + +import tools.refinery.viatra.runtime.extensibility.IQuerySpecificationProvider; + +/** + * Connector listeners are used to receive notifications on addition and removal of query specifications. + * The connector itself is also passed in the methods to allow the usage of the same listener on multiple connectors. + * + * @author Abel Hegedus + * @since 1.3 + * + */ +public interface IConnectorListener { + + /** + * Called when a new query specification is added to the given connector. + * The provider interface is used to avoid class loading as long as possible. + * + * @param connector that has a new specification + * @param specificationProvider that wraps the new specification + */ + void querySpecificationAdded(IRegistrySourceConnector connector, IQuerySpecificationProvider specificationProvider); + + /** + * Called when a query specification is removed from the given connector. + * The provider interface is used to avoid class loading as long as possible. + * + * @param connector that has a removed specification + * @param specificationProvider that wraps the removed specification + */ + void querySpecificationRemoved(IRegistrySourceConnector connector, IQuerySpecificationProvider specificationProvider); +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/IDefaultRegistryView.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/IDefaultRegistryView.java new file mode 100644 index 00000000..9e3e0394 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/IDefaultRegistryView.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Abel Hegedus, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.registry; + +import java.util.NoSuchElementException; + +import tools.refinery.viatra.runtime.api.IQueryGroup; + +/** + * The default registry view ensures that the fully qualified name of entries are unique and provides an additional + * method for retrieving the query group of entries for easy initialization. + * + * @author Abel Hegedus + * @since 1.3 + * + */ +public interface IDefaultRegistryView extends IRegistryView { + + /** + * @return a query group containing all query specifications + */ + IQueryGroup getQueryGroup(); + + /** + * @param fullyQualifiedName + * of the entry that is requested + * @return the entry with the given FQN + * @throws NoSuchElementException if there is no such entry in the default view + */ + IQuerySpecificationRegistryEntry getEntry(String fullyQualifiedName); +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/IQuerySpecificationRegistry.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/IQuerySpecificationRegistry.java new file mode 100644 index 00000000..97a17f0e --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/IQuerySpecificationRegistry.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Abel Hegedus, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.registry; + +/** + * The query specification registry is used to manage query specifications provided by multiple connectors which can + * dynamically add and remove specifications. Users can read the contents of the registry through views that are also + * dynamically updated when the registry is changed by the connectors. + * + * @author Abel Hegedus + * @since 1.3 + * + */ +public interface IQuerySpecificationRegistry { + + /** + * Cannot register connectors with the same identifier twice. No change occurs if the identifier is already used. + * + * @param connector + * cannot be null + * @return false if a connector with the given identifier has already been added, true otherwise + */ + boolean addSource(IRegistrySourceConnector connector); + + /** + * Removes the connector if it was registered. No change occurs if the identifier of the connector was not used + * before. + * + * @param connector + * cannot be null + * @return false if a registered connector with the given identifier was not found, true if it was successfully + * removed + */ + boolean removeSource(IRegistrySourceConnector connector); + + /** + * Returns a default view instance that contains query specification entries that indicate their inclusion in + * default views. If there are entries with the same FQN, only the last added will be included in the view to avoid + * duplicate FQNs. + * + * @return the default view instance + */ + IDefaultRegistryView getDefaultView(); + + /** + * Creates a view which contains query specification entries that indicate their inclusion in default views. This + * view will also be incrementally updated on registry changes and accepts listeners to notify on changes. + * + * @return a new view instance + */ + IRegistryView createView(); + + /** + * Creates a view which contains registered query specifications that are considered relevant by the passed filter. + * This view will also be incrementally updated on registry changes and accepts listeners to notify on changes. + * + * @return a new filtered view instance + */ + IRegistryView createView(IRegistryViewFilter filter); + + /** + * Creates a view which is instantiated by the factory and is connected to the registry. This + * view will also be incrementally updated on registry changes and accepts listeners to notify on changes. + * + * @return a new view instance + */ + IRegistryView createView(IRegistryViewFactory factory); +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/IQuerySpecificationRegistryChangeListener.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/IQuerySpecificationRegistryChangeListener.java new file mode 100644 index 00000000..defdd2ab --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/IQuerySpecificationRegistryChangeListener.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Abel Hegedus, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.registry; + +/** + * Listener interface for providing update notifications of views to users. It is used for propagating changes from the + * query specification registry to the views and from the views to users. + * + * @author Abel Hegedus + * @since 1.3 + * + */ +public interface IQuerySpecificationRegistryChangeListener { + + /** + * Called when a new entry is added to the registry. + * + * @param entry that is added + */ + void entryAdded(IQuerySpecificationRegistryEntry entry); + + /** + * Called when an existing entry is removed from the registry. + * + * @param entry that is removed + */ + void entryRemoved(IQuerySpecificationRegistryEntry entry); + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/IQuerySpecificationRegistryEntry.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/IQuerySpecificationRegistryEntry.java new file mode 100644 index 00000000..5009d74b --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/IQuerySpecificationRegistryEntry.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Abel Hegedus, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.registry; + +import tools.refinery.viatra.runtime.extensibility.IQuerySpecificationProvider; + +/** + * The query specification registry entry interface can return the identifier of the source that added it to the + * registry. It is provider based and can delay class loading of the wrapped {@link IQuerySpecification} until needed. + * + * @author Abel Hegedus + * @since 1.3 + * + */ +public interface IQuerySpecificationRegistryEntry extends IQuerySpecificationProvider { + + /** + * @return the identifier of the registry source that contributed the specification + */ + String getSourceIdentifier(); + + /** + * Returns whether the query specification was provided by an identifiable project. + */ + boolean isFromProject(); + + /** + * Collects the name of the project that is registered this specification to the registry. + * If {@link #getSourceIdentifier()} is false, it returns null. + */ + String getSourceProjectName(); + + /** + * @return true if the entry should be included in default views (created without any filters) + */ + boolean includeInDefaultViews(); + + /** + * @return the wrapped {@link IQuerySpecificationProvider} or itself + */ + IQuerySpecificationProvider getProvider(); +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/IRegistrySourceConnector.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/IRegistrySourceConnector.java new file mode 100644 index 00000000..94c68d1b --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/IRegistrySourceConnector.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Abel Hegedus, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.registry; + +/** + * A registry source connector can provide query specifications to listeners (e.g. {@link IQuerySpecificationRegistry}). + * The connector interface does not support direct access to query specifications, instead it sends existing specifications + * to listeners on addition and sends notifications to listeners when a change occurs in the set of specifications. + * + * @author Abel Hegedus + * @since 1.3 + * + */ +public interface IRegistrySourceConnector { + + /** + * The connector must return the same identifier every time it is invoked! + * + * @return unique identifier of the connector + */ + String getIdentifier(); + + /** + * + * @return true if the specifications of the connector should be included in default views + */ + boolean includeSpecificationsInDefaultViews(); + + /** + * Add a listener to get updates on changes in the query specifications available from the connector. When the + * listener is added, the connector is expected to call the listener with each existing query specification. + * + * @param listener that should be added + */ + void addListener(IConnectorListener listener); + + /** + * Removes an already registered listener and stops sending updates. The connector is not required to send any + * updates before returning from this method, but should not send any events after this method returns. + * + * @param listener that should be removed + */ + void removeListener(IConnectorListener listener); +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/IRegistryView.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/IRegistryView.java new file mode 100644 index 00000000..acf49b76 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/IRegistryView.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Abel Hegedus, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.registry; + +import java.util.Set; + +/** + * The registry view is the primary interface for users to interact with the query specifications in an + * {@link IQuerySpecificationRegistry}. Views are created using the createView methods of registry and their content is + * also dynamically updated by the registry. + * + * The view contains a set of {@link IQuerySpecificationRegistryEntry} objects that can be used to access the query + * specifications themselves through the get() method. + * + * Users can check the contents of the view and add listeners to get notifications on view changes (added or removed + * entries). + * + * @author Abel Hegedus + * @since 1.3 + * + */ +public interface IRegistryView extends IQuerySpecificationRegistryChangeListener { + + /** + * @return an immutable copy of all entries found in the view + */ + Iterable getEntries(); + + /** + * @return the set of FQNs for the query specifications in the view + */ + Set getQuerySpecificationFQNs(); + + /** + * @param fullyQualifiedName + * that is looked up in the view + * @return true if the view contains an entry with given FQN, false otherwise + */ + boolean hasQuerySpecificationFQN(String fullyQualifiedName); + + /** + * @param fullyQualifiedName + * of the entries that are requested + * @return the possible empty set of entries with the given FQN + */ + Set getEntries(String fullyQualifiedName); + + /** + * Adds a listener to the view that will be notified when an entry is added to or removed from the view. + * + * @param listener that is added + */ + void addViewListener(IQuerySpecificationRegistryChangeListener listener); + + /** + * Removes a listener that was previously added to the view. + * + * @param listener that is removed + */ + void removeViewListener(IQuerySpecificationRegistryChangeListener listener); + + /** + * @return the registry underlying the view + */ + IQuerySpecificationRegistry getRegistry(); +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/IRegistryViewFactory.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/IRegistryViewFactory.java new file mode 100644 index 00000000..990902d3 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/IRegistryViewFactory.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Abel Hegedus, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.registry; + +/** + * This interface can be used to ask the registry to construct specific view instances. The factory is responsible for + * instantiating the view, but the registry is responsible for establishing the connection with the internal data store + * and to fill up the view with entries. Instances of the factory are intended to be passed to + * {@link IQuerySpecificationRegistry#createView(IRegistryViewFactory)} and only the view instance returned by that + * method can be considered initialized. + * + * @author Abel Hegedus + * @since 1.3 + * + */ +public interface IRegistryViewFactory { + + /** + * Instantiate a new view object and store the reference to the registry. + * This method should only be called by an {@link IQuerySpecificationRegistry}. + * + * @param registry that will be connected to the view + * @return the new instance of the view + * @noreference This method is not intended to be referenced by clients. + */ + IRegistryView createView(IQuerySpecificationRegistry registry); + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/IRegistryViewFilter.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/IRegistryViewFilter.java new file mode 100644 index 00000000..fa13327f --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/IRegistryViewFilter.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Abel Hegedus, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.registry; + +/** + * The registry view filter can control which entries are added and removed from an {@link IRegistryView}. + * + * @author Abel Hegedus + * @since 1.3 + * + */ +public interface IRegistryViewFilter { + + /** + * This method controls whether a registry entry is added to the view or not. The filtering is called before + * checking the uniqueness of fully qualified names, and relevant entries can overwrite existing entries with the + * same FQN. + * + * Note that filters should usually return the same value for the same entry on multiple invocations. + * + * @param entry + * that is checked + * @return true, if the entry is relevant for the view, false otherwise + */ + boolean isEntryRelevant(IQuerySpecificationRegistryEntry entry); + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/QuerySpecificationRegistry.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/QuerySpecificationRegistry.java new file mode 100644 index 00000000..139dde1c --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/QuerySpecificationRegistry.java @@ -0,0 +1,112 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Abel Hegedus, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.registry; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import tools.refinery.viatra.runtime.registry.impl.QuerySpecificationRegistryImpl; + +/** + * Registry for query specifications that can be accessed using fully qualified names through views. + * Additional query specifications can be added using {@link IRegistrySourceConnector}s. + * + *

    + * When running as an OSGi plug-in, the generated query specifications registered through extensions are automatically loaded + * into the registry by the {@link ExtensionBasedQuerySpecificationLoader} class. + * + * @author Abel Hegedus + * @since 1.3 + * + */ +public class QuerySpecificationRegistry implements IQuerySpecificationRegistry { + + private static final QuerySpecificationRegistry INSTANCE = new QuerySpecificationRegistry(); + + /** + * @return the singleton query specification registry instance + */ + public static IQuerySpecificationRegistry getInstance() { + return INSTANCE; + } + + private final QuerySpecificationRegistryImpl internalRegistry; + /** + * Connectors that should not be immediately loaded into the registry. + */ + private final Set delayedConnectors; + + /** + * Hidden constructor for singleton instance + */ + protected QuerySpecificationRegistry() { + this.internalRegistry = new QuerySpecificationRegistryImpl(); + this.delayedConnectors = new HashSet<>(); + + } + + /** + * @return the internal registry after adding delayed source connectors + */ + protected IQuerySpecificationRegistry getInternalRegistry() { + if(!delayedConnectors.isEmpty()) { + final Iterator it = delayedConnectors.iterator(); + while (it.hasNext()) { + final IRegistrySourceConnector connector = it.next(); + internalRegistry.addSource(connector); + it.remove(); + } + } + return internalRegistry; + } + + /** + * When the registry adds itself as a listener to connectors, it must send all specification providers + * to the registry. However, when {@link ExtensionBasedQuerySpecificationLoader} is triggered during the + * activation of the plugin, the individual query specification classes cannot be loaded yet. To avoid this, + * the connector of the loader is delayed until needed. + * + * @param connector that should be delayed before adding to the registry + */ + protected void addDelayedSourceConnector(IRegistrySourceConnector connector) { + delayedConnectors.add(connector); + } + + @Override + public boolean addSource(IRegistrySourceConnector connector) { + return getInternalRegistry().addSource(connector); + } + + @Override + public boolean removeSource(IRegistrySourceConnector connector) { + return getInternalRegistry().removeSource(connector); + } + + @Override + public IDefaultRegistryView getDefaultView() { + return getInternalRegistry().getDefaultView(); + } + + @Override + public IRegistryView createView() { + return getInternalRegistry().createView(); + } + + @Override + public IRegistryView createView(IRegistryViewFilter filter) { + return getInternalRegistry().createView(filter); + } + + @Override + public IRegistryView createView(IRegistryViewFactory factory) { + return getInternalRegistry().createView(factory); + } + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/connector/AbstractRegistrySourceConnector.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/connector/AbstractRegistrySourceConnector.java new file mode 100644 index 00000000..0e2ef273 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/connector/AbstractRegistrySourceConnector.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Abel Hegedus, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.registry.connector; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import tools.refinery.viatra.runtime.registry.IConnectorListener; +import tools.refinery.viatra.runtime.registry.IRegistrySourceConnector; + +/** + * Abstract registry source connector implementation that stores the identifier and listener set. + * + * + * @author Abel Hegedus + * @since 1.3 + * + */ +public abstract class AbstractRegistrySourceConnector implements IRegistrySourceConnector { + + protected Set listeners; + private String identifier; + private boolean includeInDefaultViews; + + /** + * Creates an instance of the connector with the given identifier. The identifier should be unique if you want to + * add it to a registry as a source. + * + * @param identifier + * of the newly created connector + * @param includeInDefaultViews + * true if the specifications in the connector should be included in default views + */ + public AbstractRegistrySourceConnector(String identifier, boolean includeInDefaultViews) { + super(); + Objects.requireNonNull(identifier, "Identifier must not be null!"); + this.identifier = identifier; + this.includeInDefaultViews = includeInDefaultViews; + this.listeners = new HashSet<>(); + } + + @Override + public String getIdentifier() { + return identifier; + } + + @Override + public boolean includeSpecificationsInDefaultViews() { + return includeInDefaultViews; + } + + @Override + public void addListener(IConnectorListener listener) { + Objects.requireNonNull(listener, "Listener must not be null!"); + boolean added = listeners.add(listener); + if (added) { + sendQuerySpecificationsToListener(listener); + } + } + + @Override + public void removeListener(IConnectorListener listener) { + Objects.requireNonNull(listener, "Listener must not be null!"); + listeners.remove(listener); + } + + /** + * Subclasses should send add notifications for each specification in the connector to the given listener. + * + * @param listener that should receive the notifications + */ + protected abstract void sendQuerySpecificationsToListener(IConnectorListener listener); + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/connector/QueryGroupProviderSourceConnector.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/connector/QueryGroupProviderSourceConnector.java new file mode 100644 index 00000000..e6f0adf0 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/connector/QueryGroupProviderSourceConnector.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Abel Hegedus, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.registry.connector; + +import java.util.Objects; + +import tools.refinery.viatra.runtime.extensibility.IQueryGroupProvider; +import tools.refinery.viatra.runtime.extensibility.IQuerySpecificationProvider; +import tools.refinery.viatra.runtime.registry.IConnectorListener; + +/** + * Source connector implementation that uses a {@link IQueryGroupProvider} to provide a query specifications into the + * registry. The query group can be later updated which triggers the removal of all specifications of the old group and + * the addition of all specifications from the new group. + * + * @author Abel Hegedus + * @since 1.3 + * + */ +public class QueryGroupProviderSourceConnector extends AbstractRegistrySourceConnector { + + IQueryGroupProvider queryGroupProvider; + + /** + * Creates an instance of the connector with the given identifier and the query group provider. The identifier + * should be unique if you want to add it to a registry as a source. + * + * @param identifier + * of the newly created connector + * @param provider + * that contains the query specifications handled by the connector + * @param includeInDefaultViews + * true if the specifications in the connector should be included in default views + */ + public QueryGroupProviderSourceConnector(String identifier, IQueryGroupProvider provider, boolean includeInDefaultViews) { + super(identifier, includeInDefaultViews); + this.queryGroupProvider = provider; + } + + /** + * Update the query group of the connector, which triggers the removal of all specifications on the old group and + * addition of all specifications in the given group. + * + * @param queryGroupProvider + * the queryGroupProvider to set + * @param includeInDefaultViews + * true if the specifications in the connector should be included in default views + */ + public void setQueryGroupProvider(IQueryGroupProvider queryGroupProvider) { + Objects.requireNonNull(queryGroupProvider, "Query group provider must not be null!"); + IQueryGroupProvider oldProvider = this.queryGroupProvider; + + for (IQuerySpecificationProvider specificationProvider : oldProvider.getQuerySpecificationProviders()) { + for (IConnectorListener iConnectorListener : listeners) { + iConnectorListener.querySpecificationRemoved(this, specificationProvider); + } + } + for (IQuerySpecificationProvider specificationProvider : queryGroupProvider.getQuerySpecificationProviders()) { + for (IConnectorListener iConnectorListener : listeners) { + iConnectorListener.querySpecificationAdded(this, specificationProvider); + } + } + + this.queryGroupProvider = queryGroupProvider; + } + + @Override + protected void sendQuerySpecificationsToListener(IConnectorListener listener) { + for (IQuerySpecificationProvider specificationProvider : queryGroupProvider.getQuerySpecificationProviders()) { + listener.querySpecificationAdded(this, specificationProvider); + } + } + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/connector/SpecificationMapSourceConnector.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/connector/SpecificationMapSourceConnector.java new file mode 100644 index 00000000..e5778950 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/connector/SpecificationMapSourceConnector.java @@ -0,0 +1,147 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Abel Hegedus, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.registry.connector; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Set; + +import tools.refinery.viatra.runtime.extensibility.IQuerySpecificationProvider; +import tools.refinery.viatra.runtime.extensibility.SingletonQuerySpecificationProvider; +import tools.refinery.viatra.runtime.registry.IConnectorListener; + +/** + * A simple connector implementation that allows users to simply add and remove specifications. These changes are + * propagated to listeners (e.g. the registry). Note that duplicate FQNs are not allowed in a given connector. + * + * @author Abel Hegedus + * @since 1.3 + * + */ +public class SpecificationMapSourceConnector extends AbstractRegistrySourceConnector { + + private static final String DUPLICATE_MESSAGE = "Duplicate FQN %s cannot be added to connector"; + private Map specificationProviderMap; + + /** + * Creates an instance of the connector with the given identifier. The identifier should be unique if you want to + * add it to a registry as a source. + * + * @param identifier + * of the newly created connector + * @param includeInDefaultViews + * true if the specifications in the connector should be included in default views + */ + public SpecificationMapSourceConnector(String identifier, boolean includeInDefaultViews) { + super(identifier, includeInDefaultViews); + this.specificationProviderMap = new HashMap<>(); + } + + /** + * Creates an instance of the connector with the given identifier and fills it up with the given specification + * providers. The identifier should be unique if you want to add it to a registry as a source. + * + * @param identifier + * of the newly created connector + * @param specificationProviders + * the initial set of specifications in the connector + * @param includeInDefaultViews + * true if the specifications in the connector should be included in default views + */ + public SpecificationMapSourceConnector(String identifier, Set specificationProviders, boolean includeInDefaultViews) { + this(identifier, includeInDefaultViews); + for (IQuerySpecificationProvider provider : specificationProviders) { + addQuerySpecificationProvider(provider); + } + } + + /** + * Creates an instance of the connector with the given identifier and fills it up with the specification providers + * from the given {@link SpecificationMapSourceConnector}. The identifier should be unique if you want to add it to + * a registry as a source. + * + * @param identifier + * of the newly created connector + * @param connector + * that contains the specifications to copy into the new instance + * @param includeInDefaultViews + * true if the specifications in the connector should be included in default views + */ + public SpecificationMapSourceConnector(String identifier, SpecificationMapSourceConnector connector, boolean includeInDefaultViews) { + this(identifier, includeInDefaultViews); + this.specificationProviderMap.putAll(connector.specificationProviderMap); + } + + /** + * Adds a query specification to the connector. + * If you have an {@link IQuerySpecification} object, use {@link SingletonQuerySpecificationProvider}. + * + * @param provider to add to the connector + * @throws IllegalArgumentException if the connector already contains a specification with the same FQN + */ + public void addQuerySpecificationProvider(IQuerySpecificationProvider provider) { + Objects.requireNonNull(provider, "Provider must not be null!"); + String fullyQualifiedName = provider.getFullyQualifiedName(); + if (!specificationProviderMap.containsKey(fullyQualifiedName)) { + specificationProviderMap.put(fullyQualifiedName, provider); + for (IConnectorListener listener : listeners) { + listener.querySpecificationAdded(this, provider); + } + } else { + throw new IllegalArgumentException(String.format(DUPLICATE_MESSAGE, fullyQualifiedName)); + } + } + + /** + * Remove a specification that has been added with the given FQN. + * + * @param fullyQualifiedName + * @throws NoSuchElementException if the connector does not contain a specification with the given FQN + */ + public void removeQuerySpecificationProvider(String fullyQualifiedName) { + Objects.requireNonNull(fullyQualifiedName, "Fully qualified name must not be null!"); + IQuerySpecificationProvider provider = specificationProviderMap.remove(fullyQualifiedName); + if (provider == null) { + throw new NoSuchElementException( + String.format("Connector does not contain specification with FQN %s", fullyQualifiedName)); + } + for (IConnectorListener listener : listeners) { + listener.querySpecificationRemoved(this, provider); + } + } + + /** + * @return the immutable copy of the set of FQNs for the added query specifications + */ + public Set getQuerySpecificationFQNs() { + return Collections.unmodifiableSet(new HashSet<>(specificationProviderMap.keySet())); + } + + /** + * + * @param fullyQualifiedName that is checked + * @return true if a specification with the given FQN exists in the connector, false otherwise + */ + public boolean hasQuerySpecificationFQN(String fullyQualifiedName) { + Objects.requireNonNull(fullyQualifiedName, "FQN must not be null!"); + return specificationProviderMap.containsKey(fullyQualifiedName); + } + + @Override + protected void sendQuerySpecificationsToListener(IConnectorListener listener) { + for (IQuerySpecificationProvider provider : specificationProviderMap.values()) { + listener.querySpecificationAdded(this, provider); + } + } + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/data/QuerySpecificationStore.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/data/QuerySpecificationStore.java new file mode 100644 index 00000000..649527b6 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/data/QuerySpecificationStore.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Abel Hegedus, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.registry.data; + +import java.util.Map; +import java.util.TreeMap; + +/** + * Internal data storage object that represents a query specification registry with a set of sources driven by + * connectors. The sources must have unique identifiers. + * + * @author Abel Hegedus + * + */ +public class QuerySpecificationStore { + + private Map sources; + + /** + * Creates a new instance with an empty identifier to source map. + */ + public QuerySpecificationStore() { + this.sources = new TreeMap<>(); + } + + /** + * @return the live, modifiable identifier to source map + */ + public Map getSources() { + return sources; + } +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/data/RegistryEntryImpl.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/data/RegistryEntryImpl.java new file mode 100644 index 00000000..758885e2 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/data/RegistryEntryImpl.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Abel Hegedus, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.registry.data; + +import tools.refinery.viatra.runtime.api.IQuerySpecification; +import tools.refinery.viatra.runtime.extensibility.IQuerySpecificationProvider; +import tools.refinery.viatra.runtime.registry.IQuerySpecificationRegistryEntry; + +/** + * Internal data storage object that represents a query specification entry. The entry contains an + * {@link IQuerySpecificationProvider} and has a reference to the source that contains it. + * + * @author Abel Hegedus + * + */ +public class RegistryEntryImpl implements IQuerySpecificationRegistryEntry { + + private IQuerySpecificationProvider provider; + private RegistrySourceImpl source; + + /** + * Creates a new instance with the given source and the given provider. + * + * @param source + * that contains the new entry + * @param provider + * that wraps the specification represented by the entry + */ + public RegistryEntryImpl(RegistrySourceImpl source, IQuerySpecificationProvider provider) { + this.source = source; + this.provider = provider; + } + + /** + * @return the source that contains this entry + */ + public RegistrySourceImpl getSource() { + return source; + } + + @Override + public String getSourceIdentifier() { + return source.getIdentifier(); + } + + @Override + public boolean includeInDefaultViews() { + return getSource().includeEntriesInDefaultViews(); + } + + @Override + public String getFullyQualifiedName() { + return provider.getFullyQualifiedName(); + } + + @Override + public IQuerySpecification get() { + return provider.get(); + } + + @Override + public IQuerySpecificationProvider getProvider() { + return provider; + } + + @Override + public boolean isFromProject() { + return provider.getSourceProjectName() != null; + } + + @Override + public String getSourceProjectName() { + return provider.getSourceProjectName(); + } +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/data/RegistrySourceImpl.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/data/RegistrySourceImpl.java new file mode 100644 index 00000000..83c3b920 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/data/RegistrySourceImpl.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Abel Hegedus, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.registry.data; + +import java.util.Map; +import java.util.TreeMap; + +/** + * Internal data storage object that represents a query specification source driven by a connector. The source must have + * unique identifier that is copied from the connector. The source uses a FQN to entry map to manage registry entries. + * + * @author Abel Hegedus + * + */ +public class RegistrySourceImpl { + + private String identifier; + private boolean includeInDefaultViews; + private QuerySpecificationStore querySpecificationStore; + private Map fqnToEntryMap; + + /** + * Creates a new source with the given identifier and an empty entry map. + * + * @param identifier + * for the source + * @param querySpecificationStore + * that contains this source + * @param includeInDefaultViews + * true if the entries of the source should be included in default views + */ + public RegistrySourceImpl(String identifier, QuerySpecificationStore querySpecificationStore, boolean includeInDefaultViews) { + this.identifier = identifier; + this.includeInDefaultViews = includeInDefaultViews; + this.querySpecificationStore = querySpecificationStore; + this.fqnToEntryMap = new TreeMap<>(); + } + + /** + * @return the identifier of the source + */ + public String getIdentifier() { + return identifier; + } + + /** + * @return true if the entries in the source should be included in default views + */ + public boolean includeEntriesInDefaultViews() { + return includeInDefaultViews; + } + + /** + * @return the store that contains the source + */ + public QuerySpecificationStore getStore() { + return querySpecificationStore; + } + + /** + * @return the live, modifiable FQN to entry map + */ + public Map getFqnToEntryMap() { + return fqnToEntryMap; + } + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/impl/FilteringRegistryView.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/impl/FilteringRegistryView.java new file mode 100644 index 00000000..164a30d3 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/impl/FilteringRegistryView.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Abel Hegedus, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.registry.impl; + +import tools.refinery.viatra.runtime.registry.IQuerySpecificationRegistry; +import tools.refinery.viatra.runtime.registry.IRegistryViewFilter; +import tools.refinery.viatra.runtime.registry.view.AbstractRegistryView; +import tools.refinery.viatra.runtime.registry.IQuerySpecificationRegistryEntry; + +/** + * Registry view implementation that uses a filter to delegate the decision on whether + * a given specification is relevant to the view. + * + * @author Abel Hegedus + * + */ +public class FilteringRegistryView extends AbstractRegistryView { + + private IRegistryViewFilter filter; + + /** + * Creates a new filtering view instance. + * + * @param registry that defines the view + * @param filter that is used for deciding relevancy + */ + public FilteringRegistryView(IQuerySpecificationRegistry registry, IRegistryViewFilter filter, boolean allowDuplicateFQNs) { + super(registry, allowDuplicateFQNs); + this.filter = filter; + } + + @Override + protected boolean isEntryRelevant(IQuerySpecificationRegistryEntry entry) { + return filter.isEntryRelevant(entry); + } + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/impl/GlobalRegistryView.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/impl/GlobalRegistryView.java new file mode 100644 index 00000000..75d730b6 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/impl/GlobalRegistryView.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Abel Hegedus, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.registry.impl; + +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.stream.Collectors; + +import tools.refinery.viatra.runtime.api.IQueryGroup; +import tools.refinery.viatra.runtime.api.LazyLoadingQueryGroup; +import tools.refinery.viatra.runtime.registry.IDefaultRegistryView; +import tools.refinery.viatra.runtime.registry.IQuerySpecificationRegistry; +import tools.refinery.viatra.runtime.registry.IQuerySpecificationRegistryEntry; +import tools.refinery.viatra.runtime.registry.view.AbstractRegistryView; + +/** + * Registry view implementation that considers specifications relevant if they are included in default views. + * + * @author Abel Hegedus + * + */ +public class GlobalRegistryView extends AbstractRegistryView implements IDefaultRegistryView { + + /** + * Creates a new instance of the global view. + * + * @param registry that defines the view + */ + public GlobalRegistryView(IQuerySpecificationRegistry registry) { + super(registry, false); + } + + @Override + protected boolean isEntryRelevant(IQuerySpecificationRegistryEntry entry) { + return entry.includeInDefaultViews(); + } + + @Override + public IQueryGroup getQueryGroup() { + Set allQueries = + fqnToEntryMap.distinctValuesStream().collect(Collectors.toSet()); + IQueryGroup queryGroup = LazyLoadingQueryGroup.of(allQueries); + return queryGroup; + } + + @Override + public IQuerySpecificationRegistryEntry getEntry(String fullyQualifiedName) { + Set entries = getEntries(fullyQualifiedName); + if(entries.isEmpty()){ + throw new NoSuchElementException("Cannot find entry with FQN " + fullyQualifiedName); + } + if(entries.size() > 1) { + throw new IllegalStateException("Global view must never contain duplicated FQNs!"); + } + IQuerySpecificationRegistryEntry entry = entries.iterator().next(); + return entry; + } + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/impl/QuerySpecificationRegistryImpl.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/impl/QuerySpecificationRegistryImpl.java new file mode 100644 index 00000000..63306e93 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/impl/QuerySpecificationRegistryImpl.java @@ -0,0 +1,177 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Abel Hegedus, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.registry.impl; + +import static tools.refinery.viatra.runtime.matchers.util.Preconditions.checkArgument; + +import java.util.Map; + +import org.apache.log4j.Logger; +import tools.refinery.viatra.runtime.extensibility.IQuerySpecificationProvider; +import tools.refinery.viatra.runtime.registry.IConnectorListener; +import tools.refinery.viatra.runtime.registry.IDefaultRegistryView; +import tools.refinery.viatra.runtime.registry.IQuerySpecificationRegistry; +import tools.refinery.viatra.runtime.registry.IRegistryView; +import tools.refinery.viatra.runtime.registry.IRegistryViewFactory; +import tools.refinery.viatra.runtime.registry.IRegistryViewFilter; +import tools.refinery.viatra.runtime.registry.IQuerySpecificationRegistryChangeListener; +import tools.refinery.viatra.runtime.registry.IRegistrySourceConnector; +import tools.refinery.viatra.runtime.registry.data.QuerySpecificationStore; +import tools.refinery.viatra.runtime.registry.data.RegistryEntryImpl; +import tools.refinery.viatra.runtime.registry.data.RegistrySourceImpl; +import tools.refinery.viatra.runtime.util.ViatraQueryLoggingUtil; + +/** + * This is the default implementation of the {@link IQuerySpecificationRegistry} interface. + * It uses a {@link QuerySpecificationStore} to keep track of sources and entries. + * It uses a {@link RegistryChangeMultiplexer} to update all views and a single {@link IConnectorListener} + * to subscribe to sources added to the registry. + * + * @author Abel Hegedus + * + */ +public class QuerySpecificationRegistryImpl implements IQuerySpecificationRegistry { + + private static final String CONNECTOR_NULL_MSG = "Connector cannot be null"; + private final QuerySpecificationStore querySpecificationStore; + private final IConnectorListener connectorListener; + private final RegistryChangeMultiplexer multiplexer; + private final Logger logger; + private IDefaultRegistryView defaultView = null; + + /** + * Creates a new instance of the registry + */ + public QuerySpecificationRegistryImpl() { + this.querySpecificationStore = new QuerySpecificationStore(); + this.connectorListener = new RegistryUpdaterConnectorListener(); + this.multiplexer = new RegistryChangeMultiplexer(); + this.logger = ViatraQueryLoggingUtil.getLogger(IQuerySpecificationRegistry.class); + } + + @Override + public boolean addSource(IRegistrySourceConnector connector) { + checkArgument(connector != null, CONNECTOR_NULL_MSG); + String identifier = connector.getIdentifier(); + Map sources = querySpecificationStore.getSources(); + if(sources.containsKey(identifier)){ + return false; + } + RegistrySourceImpl source = new RegistrySourceImpl(identifier, querySpecificationStore, connector.includeSpecificationsInDefaultViews()); + sources.put(identifier, source); + connector.addListener(connectorListener); + logger.debug("Source added: " + source.getIdentifier()); + return true; + } + + @Override + public boolean removeSource(IRegistrySourceConnector connector) { + checkArgument(connector != null, CONNECTOR_NULL_MSG); + String identifier = connector.getIdentifier(); + Map sources = querySpecificationStore.getSources(); + if(!sources.containsKey(identifier)){ + return false; + } + connector.removeListener(connectorListener); + RegistrySourceImpl source = sources.remove(identifier); + for (RegistryEntryImpl entry : source.getFqnToEntryMap().values()) { + multiplexer.entryRemoved(entry); + } + logger.debug("Source removed: " + source.getIdentifier()); + return true; + } + + /** + * @return the internal store of the registry + */ + protected QuerySpecificationStore getStore() { + return querySpecificationStore; + } + + @Override + public IRegistryView createView() { + return createGlobalView(); + } + + private GlobalRegistryView createGlobalView() { + GlobalRegistryView registryView = new GlobalRegistryView(this); + initializeChangeListener(registryView); + return registryView; + } + + protected void initializeChangeListener(IQuerySpecificationRegistryChangeListener listener) { + // send existing entries to aspect + for (RegistrySourceImpl source : querySpecificationStore.getSources().values()) { + Map entryMap = source.getFqnToEntryMap(); + for (RegistryEntryImpl entry : entryMap.values()) { + listener.entryAdded(entry); + } + } + multiplexer.addListener(listener); + } + + @Override + public IRegistryView createView(IRegistryViewFilter filter) { + checkArgument(filter != null, "Filter cannot be null"); + FilteringRegistryView registryView = new FilteringRegistryView(this, filter, false); + initializeChangeListener(registryView); + return registryView; + } + + /** + * Internal connector listener implementation for updating internal store and propagating changes to views. + * + * @author Abel Hegedus + * + */ + private final class RegistryUpdaterConnectorListener implements IConnectorListener { + @Override + public void querySpecificationAdded(IRegistrySourceConnector connector, IQuerySpecificationProvider specification) { + String identifier = connector.getIdentifier(); + RegistrySourceImpl source = querySpecificationStore.getSources().get(identifier); + String fullyQualifiedName = specification.getFullyQualifiedName(); + RegistryEntryImpl registryEntry = new RegistryEntryImpl(source, specification); + RegistryEntryImpl oldEntry = source.getFqnToEntryMap().put(fullyQualifiedName, registryEntry); + if(oldEntry != null) { + logger.warn(String.format("Specification added with existing FQN %s in source %s", fullyQualifiedName, identifier)); + multiplexer.entryRemoved(oldEntry); + } + multiplexer.entryAdded(registryEntry); + + } + + @Override + public void querySpecificationRemoved(IRegistrySourceConnector connector, IQuerySpecificationProvider specification) { + String identifier = connector.getIdentifier(); + RegistrySourceImpl source = querySpecificationStore.getSources().get(identifier); + String fullyQualifiedName = specification.getFullyQualifiedName(); + RegistryEntryImpl registryEntry = source.getFqnToEntryMap().remove(fullyQualifiedName); + if(registryEntry != null) { + multiplexer.entryRemoved(registryEntry); + } + } + } + + @Override + public IDefaultRegistryView getDefaultView() { + if(this.defaultView == null){ + this.defaultView = createGlobalView(); + } + return this.defaultView; + } + + @Override + public IRegistryView createView(IRegistryViewFactory factory) { + IRegistryView registryView = factory.createView(this); + initializeChangeListener(registryView); + return registryView; + } + + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/impl/RegistryChangeMultiplexer.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/impl/RegistryChangeMultiplexer.java new file mode 100644 index 00000000..450f36b8 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/impl/RegistryChangeMultiplexer.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Abel Hegedus, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.registry.impl; + +import java.util.Collections; +import java.util.Set; +import java.util.WeakHashMap; + +import tools.refinery.viatra.runtime.registry.IQuerySpecificationRegistryChangeListener; +import tools.refinery.viatra.runtime.registry.IQuerySpecificationRegistryEntry; + +/** + * Listener implementation that propagates all changes to a set of listeners. + * The listeners are stored with weak references to avoid a need for disposal. + * + * @author Abel Hegedus + * + */ +public class RegistryChangeMultiplexer implements IQuerySpecificationRegistryChangeListener { + + private Set listeners; + + /** + * Creates a new instance of the multiplexer. + */ + public RegistryChangeMultiplexer() { + this.listeners = Collections.newSetFromMap(new WeakHashMap()); + } + + /** + * Adds a weak reference on the listener to the multiplexer. The listener will receive all further notifications and + * does not have to be removed, since the multiplexer will not keep it in memory when it can be collected. + */ + public boolean addListener(IQuerySpecificationRegistryChangeListener listener) { + return listeners.add(listener); + } + + @Override + public void entryAdded(IQuerySpecificationRegistryEntry entry) { + for (IQuerySpecificationRegistryChangeListener listener : listeners) { + listener.entryAdded(entry); + } + } + + @Override + public void entryRemoved(IQuerySpecificationRegistryEntry entry) { + for (IQuerySpecificationRegistryChangeListener listener : listeners) { + listener.entryRemoved(entry); + } + } + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/view/AbstractRegistryView.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/view/AbstractRegistryView.java new file mode 100644 index 00000000..7b3c34ba --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/registry/view/AbstractRegistryView.java @@ -0,0 +1,150 @@ +/******************************************************************************* + * Copyright (c) 2010-2016, Abel Hegedus, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.registry.view; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.log4j.Logger; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory.MemoryType; +import tools.refinery.viatra.runtime.matchers.util.IMemoryView; +import tools.refinery.viatra.runtime.matchers.util.IMultiLookup; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; +import tools.refinery.viatra.runtime.registry.IQuerySpecificationRegistry; +import tools.refinery.viatra.runtime.registry.IQuerySpecificationRegistryChangeListener; +import tools.refinery.viatra.runtime.registry.IQuerySpecificationRegistryEntry; +import tools.refinery.viatra.runtime.registry.IRegistryView; +import tools.refinery.viatra.runtime.util.ViatraQueryLoggingUtil; + +/** + * An abstract {@link IRegistryView} implementation that stores the registry, the set of listeners added to the view and + * the FQN to entry map of the view itself. The only responsibility of subclasses is to decide whether an entry received + * as an addition or removal notification is relevant to the view. + * + * @author Abel Hegedus + * @since 1.3 + */ +public abstract class AbstractRegistryView implements IRegistryView { + + private static final String LISTENER_EXCEPTION_REMOVE = "Exception occurred while notifying view listener %s about entry removal"; + private static final String LISTENER_EXCEPTION_ADD = "Exception occurred while notifying view listener %s about entry addition"; + protected final IQuerySpecificationRegistry registry; + protected final IMultiLookup fqnToEntryMap; + protected final Set listeners; + protected final boolean allowDuplicateFQNs; + + /** + * This method is called both when an addition or removal notification is received from the registry. Subclasses can + * implement view filtering by returning false for those specifications that are not relevant for this view. + * + * @param entry + * that is added or removed in the registry + * @return true if the entry should be added to or removed from the view, false otherwise + */ + protected abstract boolean isEntryRelevant(IQuerySpecificationRegistryEntry entry); + + /** + * Creates a new view instance for the given registry. Note that views are created by the registry and the view + * update mechanisms are also set up by the registry. + * + * @param registry + */ + public AbstractRegistryView(IQuerySpecificationRegistry registry, boolean allowDuplicateFQNs) { + this.registry = registry; + this.allowDuplicateFQNs = allowDuplicateFQNs; + this.fqnToEntryMap = CollectionsFactory.createMultiLookup(Object.class, MemoryType.SETS, Object.class); + this.listeners = new HashSet<>(); + } + + @Override + public IQuerySpecificationRegistry getRegistry() { + return registry; + } + + @Override + public Iterable getEntries() { + return fqnToEntryMap.distinctValuesStream().collect(Collectors.toSet()); + } + + @Override + public Set getQuerySpecificationFQNs() { + return fqnToEntryMap.distinctKeysStream().collect(Collectors.toSet()); + } + + @Override + public boolean hasQuerySpecificationFQN(String fullyQualifiedName) { + Preconditions.checkArgument(fullyQualifiedName != null, "FQN must not be null!"); + return fqnToEntryMap.lookupExists(fullyQualifiedName); + } + + @Override + public Set getEntries(String fullyQualifiedName) { + Preconditions.checkArgument(fullyQualifiedName != null, "FQN must not be null!"); + IMemoryView entries = fqnToEntryMap.lookupOrEmpty(fullyQualifiedName); + return Collections.unmodifiableSet(entries.distinctValues()); + } + + @Override + public void addViewListener(IQuerySpecificationRegistryChangeListener listener) { + Preconditions.checkArgument(listener != null, "Null listener not supported"); + listeners.add(listener); + } + + @Override + public void removeViewListener(IQuerySpecificationRegistryChangeListener listener) { + Preconditions.checkArgument(listener != null, "Null listener not supported"); + listeners.remove(listener); + } + + @Override + public void entryAdded(IQuerySpecificationRegistryEntry entry) { + if (isEntryRelevant(entry)) { + String fullyQualifiedName = entry.getFullyQualifiedName(); + IMemoryView duplicates = fqnToEntryMap.lookup(fullyQualifiedName); + if(!allowDuplicateFQNs && duplicates != null) { + Set removed = new HashSet<>(duplicates.distinctValues()); + for (IQuerySpecificationRegistryEntry e : removed) { + fqnToEntryMap.removePair(fullyQualifiedName, e); + notifyListeners(e, false); + } + } + fqnToEntryMap.addPair(fullyQualifiedName, entry); + notifyListeners(entry, true); + } + } + + @Override + public void entryRemoved(IQuerySpecificationRegistryEntry entry) { + if (isEntryRelevant(entry)) { + String fullyQualifiedName = entry.getFullyQualifiedName(); + fqnToEntryMap.removePair(fullyQualifiedName, entry); + notifyListeners(entry, false); + } + } + + private void notifyListeners(IQuerySpecificationRegistryEntry entry, boolean addition) { + for (IQuerySpecificationRegistryChangeListener listener : listeners) { + try { + if(addition){ + listener.entryAdded(entry); + } else { + listener.entryRemoved(entry); + } + } catch (Exception ex) { + Logger logger = ViatraQueryLoggingUtil.getLogger(AbstractRegistryView.class); + String formatString = addition ? LISTENER_EXCEPTION_ADD : LISTENER_EXCEPTION_REMOVE; + logger.error(String.format(formatString, listener), ex); + } + } + } + +} \ No newline at end of file diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/tabular/EcoreIndexHost.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/tabular/EcoreIndexHost.java new file mode 100644 index 00000000..e87a726a --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/tabular/EcoreIndexHost.java @@ -0,0 +1,166 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.tabular; + +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EClassifier; +import org.eclipse.emf.ecore.EDataType; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.emf.ecore.EcorePackage; +import tools.refinery.viatra.runtime.api.scope.QueryScope; +import tools.refinery.viatra.runtime.emf.EMFQueryMetaContext; +import tools.refinery.viatra.runtime.emf.EMFScope; +import tools.refinery.viatra.runtime.emf.types.EClassExactInstancesKey; +import tools.refinery.viatra.runtime.emf.types.EClassTransitiveInstancesKey; +import tools.refinery.viatra.runtime.emf.types.EDataTypeInSlotsKey; +import tools.refinery.viatra.runtime.emf.types.EStructuralFeatureInstancesKey; +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.scopes.IStorageBackend; +import tools.refinery.viatra.runtime.matchers.scopes.SimpleRuntimeContext; +import tools.refinery.viatra.runtime.matchers.scopes.tables.DisjointUnionTable; +import tools.refinery.viatra.runtime.matchers.scopes.tables.ITableWriterBinary; +import tools.refinery.viatra.runtime.matchers.scopes.tables.ITableWriterUnary; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; + +/** + * Simple EMF-specific demo tabular index host. + * + *

    Usage:

      + *
    • First, instantiate index host with given Ecore metamodel packages + *
    • To emulate an EMF instance model, write arbitrary content into the tables using {@link #getTableDirectInstances(EClassifier)} and {@link #getTableFeatureSlots(EStructuralFeature)}. + *
    • Initialize and evaluate regular EMF-based Viatra queries on the scope provided by {@link #getScope()}, as you would on an {@link EMFScope}. + *
        + * + *

        + * EXPERIMENTAL. This class or interface has been added as + * part of a work in progress. There is no guarantee that this API will + * work or that it will remain the same. + * + * @author Gabor Bergmann + * @since 2.1 + */ +public class EcoreIndexHost extends TabularIndexHost { + + public EcoreIndexHost(IStorageBackend storage, EPackage... packages) { + super(storage, new SimpleRuntimeContext(EMFQueryMetaContext.DEFAULT_SURROGATE)); + + initTables(packages); + } + + @Override + protected boolean isQueryScopeEmulated(Class queryScopeClass) { + return EMFScope.class.equals(queryScopeClass); + } + + + private Map> tableDirectInstances = CollectionsFactory.createMap(); + private Map tableTransitiveInstances = CollectionsFactory.createMap(); + private Map> tableFeatures = CollectionsFactory.createMap(); + + private void initTables(EPackage[] packages) { + + // create instance tables first + for (EPackage ePackage : packages) { + for (EClassifier eClassifier : ePackage.getEClassifiers()) { + boolean unique; + IInputKey classifierKey; + if (eClassifier instanceof EClass) { + EClass eClass = (EClass) eClassifier; + + // create transitive instances table + IInputKey transitiveKey = new EClassTransitiveInstancesKey(eClass); + DisjointUnionTable transitiveTable = registerNewTable(new DisjointUnionTable(transitiveKey, runtimeContext)); + tableTransitiveInstances.put(eClass, transitiveTable); + + // process feature tables + for (EStructuralFeature feature : eClass.getEStructuralFeatures()) { + IInputKey featureKey = new EStructuralFeatureInstancesKey(feature); + ITableWriterBinary.Table featureTable = newBinaryInputTable(featureKey, feature.isUnique()); + tableFeatures.put(feature, featureTable); + } + + // direct instance table + unique = true; + classifierKey = new EClassExactInstancesKey(eClass); + } else { // datatype + unique = false; + classifierKey = new EDataTypeInSlotsKey((EDataType) eClassifier); + } + ITableWriterUnary.Table directTable = newUnaryInputTable(classifierKey, unique); + tableDirectInstances.put(eClassifier, directTable); + } + } + + // global implicit supertype EObject is always available as a transitive table + EClass eObjectClass = EcorePackage.eINSTANCE.getEObject(); + DisjointUnionTable eObjectAllInstancesTable = tableTransitiveInstances.get(eObjectClass); + if (eObjectAllInstancesTable == null) { // is it already added? + IInputKey transitiveKey = new EClassTransitiveInstancesKey(eObjectClass); + eObjectAllInstancesTable = registerNewTable(new DisjointUnionTable(transitiveKey, runtimeContext)); + tableTransitiveInstances.put(eObjectClass, eObjectAllInstancesTable); + + boolean unique = true; + IInputKey classifierKey = new EClassExactInstancesKey(eObjectClass); + ITableWriterUnary.Table directTable = newUnaryInputTable(classifierKey, unique); + tableDirectInstances.put(eObjectClass, directTable); + } + + // set up disjoint unoin tables + for (Entry entry : tableTransitiveInstances.entrySet()) { + EClass eClass = entry.getKey(); + ITableWriterUnary.Table directTable = tableDirectInstances.get(eClass); + + // the direct type itself is a child + entry.getValue().addChildTable(directTable); + + // connect supertypes + for (EClass superClass : eClass.getEAllSuperTypes()) { + DisjointUnionTable transitiveTable = tableTransitiveInstances.get(superClass); + if (transitiveTable == null) { + throw new IllegalStateException( + String.format("No index table found for EClass %s, supertype of %s", + superClass.getName(), eClass.getName()) + ); + } + transitiveTable.addChildTable(directTable); + } + // global implicit supertype + if (!eClass.equals(eObjectClass)) { + eObjectAllInstancesTable.addChildTable(directTable); + } + + } + + } + + public ITableWriterUnary.Table getTableDirectInstances(EClassifier classifier) { + return tableDirectInstances.get(classifier); + } + public ITableWriterBinary.Table getTableFeatureSlots(EStructuralFeature feature) { + return tableFeatures.get(feature); + } + + + + public Set>> getAllCurrentTablesDirectInstances() { + return Collections.unmodifiableSet(tableDirectInstances.entrySet()); + } + public Set>> getAllCurrentTablesFeatures() { + return Collections.unmodifiableSet(tableFeatures.entrySet()); + } + + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/tabular/TabularEngineContext.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/tabular/TabularEngineContext.java new file mode 100644 index 00000000..ee33d3bc --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/tabular/TabularEngineContext.java @@ -0,0 +1,107 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package tools.refinery.viatra.runtime.tabular; + +import org.apache.log4j.Logger; +import tools.refinery.viatra.runtime.api.ViatraQueryEngine; +import tools.refinery.viatra.runtime.api.scope.*; +import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext; +import tools.refinery.viatra.runtime.matchers.util.CollectionsFactory; + +import java.lang.reflect.InvocationTargetException; +import java.util.List; +import java.util.concurrent.Callable; + +/** + * @author Gabor Bergmann + * + * @since 2.1 + */ +class TabularEngineContext implements IEngineContext, IBaseIndex { + + private TabularIndexHost indexHost; + private ViatraQueryEngine engine; + private Logger logger; + + private List errorListeners = CollectionsFactory.createObserverList(); + + public TabularEngineContext(TabularIndexHost server, ViatraQueryEngine engine, + IIndexingErrorListener errorListener, Logger logger) { + this.indexHost = server; + this.engine = engine; + this.logger = logger; + + this.addIndexingErrorListener(errorListener); + } + + @Override + public IBaseIndex getBaseIndex() { + return this; + } + + @Override + public void dispose() { + // NOP, server lifecycle not controlled by engine + } + + @Override + public IQueryRuntimeContext getQueryRuntimeContext() { + return indexHost.getRuntimeContext(); + } + + @Override + public V coalesceTraversals(Callable callable) throws InvocationTargetException { + try { + return callable.call(); + } catch (Exception e) { + throw new InvocationTargetException(e); + } + } + + @Override + public void addBaseIndexChangeListener(ViatraBaseIndexChangeListener listener) { + // TODO no notifications yet + } + + @Override + public void removeBaseIndexChangeListener(ViatraBaseIndexChangeListener listener) { + // TODO no notifications yet + } + + @Override + public boolean addInstanceObserver(IInstanceObserver observer, Object observedObject) { + // TODO no notifications yet + return true; + } + + @Override + public boolean removeInstanceObserver(IInstanceObserver observer, Object observedObject) { + // TODO no notifications yet + return true; + } + + @Override + public void resampleDerivedFeatures() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addIndexingErrorListener(IIndexingErrorListener listener) { + return errorListeners.add(listener); + } + + @Override + public boolean removeIndexingErrorListener(IIndexingErrorListener listener) { + return errorListeners.remove(listener); + } + + + +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/tabular/TabularIndexHost.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/tabular/TabularIndexHost.java new file mode 100644 index 00000000..6f2a1f5f --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/tabular/TabularIndexHost.java @@ -0,0 +1,145 @@ +/******************************************************************************* + * Copyright (c) 2010-2018, Gabor Bergmann, IncQuery Labs Ltd. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.tabular; + +import tools.refinery.viatra.runtime.api.ViatraQueryEngine; +import tools.refinery.viatra.runtime.api.scope.IEngineContext; +import tools.refinery.viatra.runtime.api.scope.IIndexingErrorListener; +import tools.refinery.viatra.runtime.api.scope.QueryScope; +import tools.refinery.viatra.runtime.matchers.context.IInputKey; +import tools.refinery.viatra.runtime.matchers.scopes.IStorageBackend; +import tools.refinery.viatra.runtime.matchers.scopes.TabularRuntimeContext; +import tools.refinery.viatra.runtime.matchers.scopes.tables.IIndexTable; +import tools.refinery.viatra.runtime.matchers.scopes.tables.ITableWriterBinary; +import tools.refinery.viatra.runtime.matchers.scopes.tables.ITableWriterUnary; + +/** + * Simple tabular index host. + * + * Unlike traditional Viatra instances initialized on a model, + * this index host can be initialized and then queried, + * while its queriable "contents" (base relations) can be separately written using table writer API. + * + *

        Deriving classes are responsible for setting up the tables of this index and providing the writer API to clients. + * For the former, use {@link #newUnaryInputTable(IInputKey, boolean)}, + * {@link #newBinaryInputTable(IInputKey, boolean)} to create input tables, + * or {@link #registerNewTable(IIndexTable)} to register manually created derived tables. + * Instantiate such tables with {@link #runtimeContext} as their table context. + * + * + *

        + * EXPERIMENTAL. This class or interface has been added as + * part of a work in progress. There is no guarantee that this API will + * work or that it will remain the same. + * + * @see EcoreIndexHost EcoreIndexHost for EMF-specific example usage. + * + * @author Gabor Bergmann + * @since 2.1 + */ +public abstract class TabularIndexHost { + + private final IStorageBackend storage; + protected final TabularRuntimeContext runtimeContext; + protected final TabularIndexScope scope = new TabularIndexScope(); + + public TabularIndexHost(IStorageBackend storage, TabularRuntimeContext runtimeContext) { + this.storage = storage; + this.runtimeContext = runtimeContext; + } + + + public TabularRuntimeContext getRuntimeContext() { + return runtimeContext; + } + + public TabularIndexScope getScope() { + return scope; + } + + /** + * @return true if this index host aims to serve queries that have a scope of the given type + */ + protected abstract boolean isQueryScopeEmulated(Class queryScopeClass); + + + + /** + * Marks the beginning of an update transaction. + * In transaction mode, index table updates may be temporarily delayed for better performance. + * Stateful query backends will not update their results during the index update transaction. (TODO actually achieve this) + */ + public void startUpdateTransaction() { + storage.startTransaction(); + } + /** + * Marks the end of an update transaction. + * Any updates to index tables that were delayed during the transaction must now be flushed. + * Afterwards, stateful query backends will update their results. (TODO actually achieve this) + */ + public void finishUpdateTransaction() { + storage.finishTransaction(); + } + + /** + * To be called by deriving class. Creates and registers a new unary input table. + */ + protected ITableWriterUnary.Table newUnaryInputTable(IInputKey key, boolean unique) { + return registerNewTable(storage.createUnaryTable(key, runtimeContext, unique)); + } + /** + * To be called by deriving class. Creates and registers a new binary input table. + */ + protected ITableWriterBinary.Table newBinaryInputTable(IInputKey key, boolean unique) { + return registerNewTable(storage.createBinaryTable(key, runtimeContext, unique)); + } + /** + * To be called by deriving class. Registers the given freshly created table and also returns it for convenience. + */ + protected Table registerNewTable(Table newTable) { + runtimeContext.registerIndexTable(newTable); + return newTable; + } + + + /** + * A scope describing queries evaluated against tzhis index host. + * @author Gabor Bergmann + * + */ + public class TabularIndexScope extends QueryScope { + + public TabularIndexHost getIndexHost() { + return TabularIndexHost.this; + } + + @Override + public int hashCode() { + return getIndexHost().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof TabularIndexScope) + return getIndexHost().equals(((TabularIndexScope) obj).getIndexHost()); + return false; + } + + @Override + public boolean isCompatibleWithQueryScope(Class queryScopeClass) { + return isQueryScopeEmulated(queryScopeClass) || super.isCompatibleWithQueryScope(queryScopeClass); + } + + @Override + protected IEngineContext createEngineContext(ViatraQueryEngine engine, IIndexingErrorListener errorListener, org.apache.log4j.Logger logger) { + return new TabularEngineContext(getIndexHost(), engine, errorListener, logger); + } + + } +} diff --git a/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/util/ViatraQueryLoggingUtil.java b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/util/ViatraQueryLoggingUtil.java new file mode 100644 index 00000000..229801f8 --- /dev/null +++ b/subprojects/viatra-runtime/src/main/java/tools/refinery/viatra/runtime/util/ViatraQueryLoggingUtil.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (c) 2010-2013, Bergmann Gabor, Istvan Rath and Daniel Varro + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-v20.html. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package tools.refinery.viatra.runtime.util; + +import org.apache.log4j.Logger; +import tools.refinery.viatra.runtime.matchers.util.Preconditions; + +/** + * Centralized logger of the VIATRA Query runtime. + * @author Bergmann Gabor + * + */ +public class ViatraQueryLoggingUtil { + + private ViatraQueryLoggingUtil() {/*Utility class constructor*/} + + private static Logger externalLogger; + + public static void setExternalLogger(Logger externalLogger) { + Preconditions.checkArgument(externalLogger != null, "Must not set up null logger"); + ViatraQueryLoggingUtil.externalLogger = externalLogger; + } + /** + * Provides a static default logger. + */ + public static Logger getDefaultLogger() { + if (defaultRuntimeLogger == null) { + Logger parentLogger = externalLogger; + if (parentLogger == null) { + defaultRuntimeLogger = Logger.getLogger("org.eclipse.viatra"); + } else { + defaultRuntimeLogger = Logger.getLogger(parentLogger.getName() + ".runtime"); + } + if (defaultRuntimeLogger == null) + throw new AssertionError("Configuration error: unable to create default VIATRA Query runtime logger."); + } + + return defaultRuntimeLogger; + } + + private static String getLoggerClassname(Class clazz) { + return clazz.getName().startsWith(getDefaultLogger().getName()) + ? clazz.getName() + : getDefaultLogger().getName() + "." + clazz.getName(); + } + + /** + * Provides a class-specific logger that also stores the global logger settings of the VIATRA Query runtime + * @param clazz + */ + public static Logger getLogger(Class clazz) { + return Logger.getLogger(getLoggerClassname(clazz)); + } + + /** + * Provides a named logger that also stores the global logger settings of the VIATRA Query runtime + * @param clazz + * @param name a non-empty name to append to the class names + * @since 2.5 + */ + public static Logger getLogger(Class clazz, String name) { + return Logger.getLogger(getLoggerClassname(clazz) + '.' + name); + } + + private static Logger defaultRuntimeLogger; +} -- cgit v1.2.3-70-g09d2