From 97b0c4c1192fe5580a7957c844acc8092b56c604 Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Sat, 16 Sep 2023 13:19:31 +0200 Subject: chore: remove VIATRA branding Rename VIATRA subprojects to Refinery Interpreter to avoid interfering with Eclipse Foundation trademarks. Uses refering to a specific (historical) version of VIATRA were kept to avoid ambiguity. --- .../refinery/interpreter/CancellationToken.java | 13 + .../interpreter/api/AdvancedInterpreterEngine.java | 340 ++++++++++ .../interpreter/api/GenericPatternMatch.java | 166 +++++ .../interpreter/api/GenericPatternMatcher.java | 84 +++ .../interpreter/api/GenericQueryGroup.java | 82 +++ .../interpreter/api/GenericQuerySpecification.java | 75 +++ .../interpreter/api/IMatchUpdateListener.java | 37 ++ .../refinery/interpreter/api/IPatternMatch.java | 107 ++++ .../refinery/interpreter/api/IQueryGroup.java | 46 ++ .../interpreter/api/IQuerySpecification.java | 86 +++ .../interpreter/api/InterpreterEngine.java | 107 ++++ .../InterpreterEngineInitializationListener.java | 26 + .../api/InterpreterEngineLifecycleListener.java | 52 ++ .../interpreter/api/InterpreterEngineOptions.java | 293 +++++++++ .../interpreter/api/InterpreterMatcher.java | 258 ++++++++ .../api/InterpreterModelUpdateListener.java | 55 ++ .../interpreter/api/MatchUpdateAdapter.java | 105 ++++ .../api/impl/BaseGeneratedPatternGroup.java | 31 + .../refinery/interpreter/api/impl/BaseMatcher.java | 350 +++++++++++ .../interpreter/api/impl/BasePatternMatch.java | 91 +++ .../interpreter/api/impl/BaseQueryGroup.java | 33 + .../api/impl/BaseQuerySpecification.java | 146 +++++ .../refinery/interpreter/api/scope/IBaseIndex.java | 91 +++ .../interpreter/api/scope/IEngineContext.java | 49 ++ .../api/scope/IIndexingErrorListener.java | 23 + .../interpreter/api/scope/IInstanceObserver.java | 21 + .../scope/InterpreterBaseIndexChangeListener.java | 35 ++ .../refinery/interpreter/api/scope/QueryScope.java | 34 + .../exception/InterpreterException.java | 67 ++ .../internal/apiimpl/EngineContextFactory.java | 24 + .../internal/apiimpl/InterpreterEngineImpl.java | 689 +++++++++++++++++++++ .../internal/apiimpl/QueryResultWrapper.java | 32 + .../internal/engine/LifecycleProvider.java | 138 +++++ .../internal/engine/ListenerContainer.java | 45 ++ .../internal/engine/ModelUpdateProvider.java | 204 ++++++ .../matchers/InterpreterRuntimeException.java | 45 ++ .../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 ++ .../interpreter/matchers/aggregators/avg.java | 39 ++ .../interpreter/matchers/aggregators/count.java | 33 + .../interpreter/matchers/aggregators/max.java | 44 ++ .../interpreter/matchers/aggregators/min.java | 44 ++ .../interpreter/matchers/aggregators/sum.java | 39 ++ .../matchers/algorithms/OrderedIterableMerge.java | 83 +++ .../interpreter/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 + .../matchers/backend/IQueryBackend.java | 69 +++ .../matchers/backend/IQueryBackendFactory.java | 53 ++ .../backend/IQueryBackendFactoryProvider.java | 46 ++ .../backend/IQueryBackendHintProvider.java | 32 + .../matchers/backend/IQueryResultProvider.java | 202 ++++++ .../interpreter/matchers/backend/IUpdateable.java | 27 + .../matchers/backend/QueryEvaluationHint.java | 238 +++++++ .../matchers/backend/QueryHintOption.java | 122 ++++ .../matchers/backend/ResultProviderRequestor.java | 74 +++ .../matchers/context/AbstractQueryMetaContext.java | 69 +++ .../context/AbstractQueryRuntimeContext.java | 21 + .../interpreter/matchers/context/IInputKey.java | 45 ++ .../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 | 287 +++++++++ .../context/IQueryRuntimeContextListener.java | 27 + .../matchers/context/IndexingService.java | 38 ++ .../matchers/context/InputKeyImplication.java | 103 +++ .../context/common/BaseInputKeyWrapper.java | 55 ++ .../context/common/JavaTransitiveInstancesKey.java | 174 ++++++ .../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 +++ .../interpreter/matchers/planning/SubPlan.java | 240 +++++++ .../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 +++ .../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 + .../matchers/psystem/IQueryReference.java | 33 + .../matchers/psystem/IRelationEvaluator.java | 47 ++ .../matchers/psystem/ITypeConstraint.java | 65 ++ .../psystem/ITypeInfoProviderConstraint.java | 28 + .../matchers/psystem/IValueProvider.java | 27 + .../matchers/psystem/InitializablePQuery.java | 56 ++ .../psystem/KeyedEnumerablePConstraint.java | 39 ++ .../interpreter/matchers/psystem/PBody.java | 288 +++++++++ .../interpreter/matchers/psystem/PConstraint.java | 70 +++ .../interpreter/matchers/psystem/PTraceable.java | 16 + .../interpreter/matchers/psystem/PVariable.java | 203 ++++++ .../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 ++ .../matchers/psystem/queries/PProblem.java | 68 ++ .../matchers/psystem/queries/PQueries.java | 109 ++++ .../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 | 305 +++++++++ .../psystem/rewriters/PBodyNormalizer.java | 310 +++++++++ .../psystem/rewriters/PDisjunctionRewriter.java | 27 + .../rewriters/PDisjunctionRewriterCacher.java | 64 ++ .../psystem/rewriters/PQueryFlattener.java | 251 ++++++++ .../psystem/rewriters/RewriterException.java | 31 + .../psystem/rewriters/SurrogateQueryRewriter.java | 63 ++ .../VariableMappingExpressionEvaluatorWrapper.java | 90 +++ .../interpreter/matchers/tuple/AbstractTuple.java | 136 ++++ .../interpreter/matchers/tuple/BaseFlatTuple.java | 20 + .../matchers/tuple/BaseLeftInheritanceTuple.java | 65 ++ .../interpreter/matchers/tuple/FlatTuple.java | 60 ++ .../interpreter/matchers/tuple/FlatTuple0.java | 46 ++ .../interpreter/matchers/tuple/FlatTuple1.java | 44 ++ .../interpreter/matchers/tuple/FlatTuple2.java | 51 ++ .../interpreter/matchers/tuple/FlatTuple3.java | 55 ++ .../interpreter/matchers/tuple/FlatTuple4.java | 59 ++ .../matchers/tuple/IModifiableTuple.java | 27 + .../interpreter/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 +++ .../interpreter/matchers/tuple/MaskedTuple.java | 48 ++ .../refinery/interpreter/matchers/tuple/Tuple.java | 69 +++ .../interpreter/matchers/tuple/TupleMask.java | 560 +++++++++++++++++ .../interpreter/matchers/tuple/TupleMask0.java | 56 ++ .../matchers/tuple/TupleMaskIdentity.java | 51 ++ .../matchers/tuple/TupleValueProvider.java | 48 ++ .../interpreter/matchers/tuple/Tuples.java | 157 +++++ .../matchers/tuple/VolatileMaskedTuple.java | 50 ++ .../tuple/VolatileModifiableMaskedTuple.java | 47 ++ .../interpreter/matchers/tuple/VolatileTuple.java | 47 ++ .../interpreter/matchers/util/Accuracy.java | 48 ++ .../interpreter/matchers/util/Clearable.java | 23 + .../matchers/util/CollectionsFactory.java | 188 ++++++ .../interpreter/matchers/util/Direction.java | 61 ++ .../matchers/util/EclipseCollectionsBagMemory.java | 86 +++ .../matchers/util/EclipseCollectionsDeltaBag.java | 41 ++ .../matchers/util/EclipseCollectionsFactory.java | 157 +++++ .../util/EclipseCollectionsLongMultiset.java | 150 +++++ .../util/EclipseCollectionsLongSetMemory.java | 216 +++++++ .../util/EclipseCollectionsMultiLookup.java | 226 +++++++ .../matchers/util/EclipseCollectionsMultiset.java | 93 +++ .../matchers/util/EclipseCollectionsSetMemory.java | 94 +++ .../interpreter/matchers/util/EmptyMemory.java | 93 +++ .../refinery/interpreter/matchers/util/ICache.java | 32 + .../interpreter/matchers/util/IDeltaBag.java | 26 + .../interpreter/matchers/util/IMemory.java | 81 +++ .../interpreter/matchers/util/IMemoryView.java | 205 ++++++ .../interpreter/matchers/util/IMultiLookup.java | 216 +++++++ .../matchers/util/IMultiLookupAbstract.java | 483 +++++++++++++++ .../interpreter/matchers/util/IMultiset.java | 30 + .../interpreter/matchers/util/IProvider.java | 30 + .../interpreter/matchers/util/ISetMemory.java | 37 ++ .../matchers/util/MapBackedMemoryView.java | 102 +++ .../interpreter/matchers/util/MarkedMemory.java | 21 + .../matchers/util/MemoryViewBackedMapView.java | 117 ++++ .../interpreter/matchers/util/Preconditions.java | 208 +++++++ .../interpreter/matchers/util/PurgableCache.java | 44 ++ .../refinery/interpreter/matchers/util/Sets.java | 90 +++ .../refinery/interpreter/matchers/util/Signed.java | 60 ++ .../matchers/util/SingletonInstanceProvider.java | 29 + .../matchers/util/SingletonMemoryView.java | 105 ++++ .../interpreter/matchers/util/TimelyMemory.java | 517 ++++++++++++++++ .../matchers/util/resumable/MaskedResumable.java | 36 ++ .../matchers/util/resumable/Resumable.java | 27 + .../matchers/util/resumable/UnmaskedResumable.java | 36 ++ .../matchers/util/timeline/CompactTimeline.java | 111 ++++ .../interpreter/matchers/util/timeline/Diff.java | 55 ++ .../matchers/util/timeline/SingletonTimeline.java | 73 +++ .../matchers/util/timeline/Timeline.java | 146 +++++ .../matchers/util/timeline/Timelines.java | 46 ++ .../interpreter/util/InterpreterLoggingUtil.java | 62 ++ 249 files changed, 23636 insertions(+) create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/CancellationToken.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/AdvancedInterpreterEngine.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/GenericPatternMatch.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/GenericPatternMatcher.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/GenericQueryGroup.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/GenericQuerySpecification.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/IMatchUpdateListener.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/IPatternMatch.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/IQueryGroup.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/IQuerySpecification.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/InterpreterEngine.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/InterpreterEngineInitializationListener.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/InterpreterEngineLifecycleListener.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/InterpreterEngineOptions.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/InterpreterMatcher.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/InterpreterModelUpdateListener.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/MatchUpdateAdapter.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/impl/BaseGeneratedPatternGroup.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/impl/BaseMatcher.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/impl/BasePatternMatch.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/impl/BaseQueryGroup.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/impl/BaseQuerySpecification.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/scope/IBaseIndex.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/scope/IEngineContext.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/scope/IIndexingErrorListener.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/scope/IInstanceObserver.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/scope/InterpreterBaseIndexChangeListener.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/scope/QueryScope.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/exception/InterpreterException.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/internal/apiimpl/EngineContextFactory.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/internal/apiimpl/InterpreterEngineImpl.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/internal/apiimpl/QueryResultWrapper.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/internal/engine/LifecycleProvider.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/internal/engine/ListenerContainer.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/internal/engine/ModelUpdateProvider.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/InterpreterRuntimeException.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/AverageAccumulator.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/DoubleAverageOperator.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/DoubleSumOperator.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/ExtremumOperator.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/IntegerAverageOperator.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/IntegerSumOperator.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/LongAverageOperator.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/LongSumOperator.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/avg.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/count.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/max.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/min.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/sum.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/algorithms/OrderedIterableMerge.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/algorithms/UnionFind.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/algorithms/UnionFindNodeProperty.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/CommonQueryHintOptions.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/ICallDelegationStrategy.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/IMatcherCapability.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/IQueryBackend.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/IQueryBackendFactory.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/IQueryBackendFactoryProvider.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/IQueryBackendHintProvider.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/IQueryResultProvider.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/IUpdateable.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/QueryEvaluationHint.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/QueryHintOption.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/ResultProviderRequestor.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/AbstractQueryMetaContext.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/AbstractQueryRuntimeContext.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/IInputKey.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/IPosetComparator.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/IQueryBackendContext.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/IQueryCacheContext.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/IQueryMetaContext.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/IQueryResultProviderAccess.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/IQueryRuntimeContext.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/IQueryRuntimeContextListener.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/IndexingService.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/InputKeyImplication.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/common/BaseInputKeyWrapper.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/common/JavaTransitiveInstancesKey.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/surrogate/SurrogateQueryRegistry.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/memories/AbstractTrivialMaskedMemory.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/memories/DefaultMaskedTupleMemory.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/memories/IdentityMaskedTupleMemory.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/memories/MaskedTupleMemory.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/memories/NullaryMaskedTupleMemory.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/memories/UnaryMaskedTupleMemory.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/memories/timely/AbstractTimelyMaskedMemory.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/memories/timely/AbstractTimelyTrivialMaskedMemory.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/memories/timely/TimelyDefaultMaskedTupleMemory.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/memories/timely/TimelyIdentityMaskedTupleMemory.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/memories/timely/TimelyNullaryMaskedTupleMemory.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/memories/timely/TimelyUnaryMaskedTupleMemory.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/IOperationCompiler.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/IQueryPlannerStrategy.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/QueryProcessingException.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/SubPlan.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/SubPlanFactory.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/helpers/BuildHelper.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/helpers/FunctionalDependencyHelper.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/helpers/StatisticsHelper.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/helpers/TypeHelper.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/operations/PApply.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/operations/PEnumerate.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/operations/PJoin.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/operations/POperation.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/operations/PProject.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/operations/PStart.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/BasePConstraint.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/DeferredPConstraint.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/EnumerablePConstraint.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/IExpressionEvaluator.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/IMultiQueryReference.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/IQueryReference.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/IRelationEvaluator.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/ITypeConstraint.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/ITypeInfoProviderConstraint.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/IValueProvider.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/InitializablePQuery.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/KeyedEnumerablePConstraint.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/PBody.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/PConstraint.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/PTraceable.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/PVariable.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/TypeJudgement.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/VariableDeferredPConstraint.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/aggregations/AbstractMemorylessAggregationOperator.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/aggregations/AggregatorType.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/aggregations/BoundAggregator.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/aggregations/IAggregatorFactory.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/aggregations/IMultisetAggregationOperator.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/analysis/QueryAnalyzer.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/annotations/PAnnotation.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/annotations/ParameterReference.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicdeferred/AggregatorConstraint.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicdeferred/BaseTypeSafeConstraint.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicdeferred/Equality.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicdeferred/ExportedParameter.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicdeferred/ExpressionEvaluation.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicdeferred/Inequality.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicdeferred/NegativePatternCall.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicdeferred/PatternCallBasedDeferred.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicdeferred/PatternMatchCounter.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicdeferred/RelationEvaluation.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicdeferred/TypeFilterConstraint.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicenumerables/AbstractTransitiveClosure.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicenumerables/BinaryReflexiveTransitiveClosure.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicenumerables/BinaryTransitiveClosure.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicenumerables/Connectivity.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicenumerables/ConstantValue.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicenumerables/PositivePatternCall.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicenumerables/RepresentativeElectionConstraint.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicenumerables/TypeConstraint.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/queries/BasePQuery.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/queries/PDisjunction.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/queries/PParameter.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/queries/PParameterDirection.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/queries/PProblem.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/queries/PQueries.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/queries/PQuery.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/queries/PQueryHeader.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/queries/PVisibility.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/queries/QueryInitializationException.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/AbstractRewriterTraceSource.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/ConstraintRemovalReason.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/DefaultFlattenCallPredicate.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/FlattenerCopier.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/IConstraintFilter.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/IDerivativeModificationReason.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/IFlattenCallPredicate.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/IPTraceableTraceProvider.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/IRewriterTraceCollector.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/IVariableRenamer.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/MappingTraceCollector.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/NeverFlattenCallPredicate.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/NopTraceCollector.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/PBodyCopier.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/PBodyNormalizer.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/PDisjunctionRewriter.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/PDisjunctionRewriterCacher.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/PQueryFlattener.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/RewriterException.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/SurrogateQueryRewriter.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/VariableMappingExpressionEvaluatorWrapper.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/AbstractTuple.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/BaseFlatTuple.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/BaseLeftInheritanceTuple.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/FlatTuple.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/FlatTuple0.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/FlatTuple1.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/FlatTuple2.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/FlatTuple3.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/FlatTuple4.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/IModifiableTuple.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/ITuple.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/LeftInheritanceTuple.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/LeftInheritanceTuple1.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/LeftInheritanceTuple2.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/LeftInheritanceTuple3.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/LeftInheritanceTuple4.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/MaskedTuple.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/Tuple.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/TupleMask.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/TupleMask0.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/TupleMaskIdentity.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/TupleValueProvider.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/Tuples.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/VolatileMaskedTuple.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/VolatileModifiableMaskedTuple.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/VolatileTuple.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/Accuracy.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/Clearable.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/CollectionsFactory.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/Direction.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/EclipseCollectionsBagMemory.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/EclipseCollectionsDeltaBag.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/EclipseCollectionsFactory.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/EclipseCollectionsLongMultiset.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/EclipseCollectionsLongSetMemory.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/EclipseCollectionsMultiLookup.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/EclipseCollectionsMultiset.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/EclipseCollectionsSetMemory.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/EmptyMemory.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/ICache.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/IDeltaBag.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/IMemory.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/IMemoryView.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/IMultiLookup.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/IMultiLookupAbstract.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/IMultiset.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/IProvider.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/ISetMemory.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/MapBackedMemoryView.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/MarkedMemory.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/MemoryViewBackedMapView.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/Preconditions.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/PurgableCache.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/Sets.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/Signed.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/SingletonInstanceProvider.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/SingletonMemoryView.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/TimelyMemory.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/resumable/MaskedResumable.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/resumable/Resumable.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/resumable/UnmaskedResumable.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/timeline/CompactTimeline.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/timeline/Diff.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/timeline/SingletonTimeline.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/timeline/Timeline.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/timeline/Timelines.java create mode 100644 subprojects/interpreter/src/main/java/tools/refinery/interpreter/util/InterpreterLoggingUtil.java (limited to 'subprojects/interpreter/src/main/java/tools') diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/CancellationToken.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/CancellationToken.java new file mode 100644 index 00000000..9de7700b --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/CancellationToken.java @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.interpreter; + +@FunctionalInterface +public interface CancellationToken { + CancellationToken NONE = () -> {}; + + void checkCancelled(); +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/AdvancedInterpreterEngine.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/AdvancedInterpreterEngine.java new file mode 100644 index 00000000..5d0261d2 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/AdvancedInterpreterEngine.java @@ -0,0 +1,340 @@ +/******************************************************************************* + * 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.interpreter.api; + +import tools.refinery.interpreter.api.scope.QueryScope; +import tools.refinery.interpreter.internal.apiimpl.InterpreterEngineImpl; +import tools.refinery.interpreter.matchers.InterpreterRuntimeException; +import tools.refinery.interpreter.matchers.backend.*; + +import java.lang.reflect.InvocationTargetException; +import java.util.concurrent.Callable; + +/** + * Advanced interface to a Refinery Interpreter incremental evaluation engine. + * + *

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

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

+ * + * @author Bergmann Gabor + * @noextend This class is not intended to be subclassed by clients. + */ +public abstract class AdvancedInterpreterEngine extends InterpreterEngine { + + /** + * Creates a new unmanaged Refinery Interpreter 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 AdvancedInterpreterEngine}. + * + *

+ * 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 AdvancedInterpreterEngine createUnmanagedEngine(QueryScope scope) { + return new InterpreterEngineImpl(scope); + } + + /** + * Creates a new unmanaged Refinery Intepreter 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 AdvancedInterpreterEngine}. + * + *

+ * 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 AdvancedInterpreterEngine createUnmanagedEngine(QueryScope scope, InterpreterEngineOptions options) { + return new InterpreterEngineImpl(scope, options); + } + + /** + * Provides access to a given existing engine through the advanced interface. + * + * @param engine + * the engine to access using the advanced interface + * @return a reference to the same engine conforming to the advanced interface + */ + public static AdvancedInterpreterEngine from(InterpreterEngine engine) { + return (AdvancedInterpreterEngine) engine; + } + + /** + * Add an engine lifecycle listener to this engine instance. + * + * @param listener + * the {@link InterpreterEngineLifecycleListener} that should listen to lifecycle events from this engine + */ + public abstract void addLifecycleListener(InterpreterEngineLifecycleListener listener); + + /** + * Remove an existing lifecycle listener from this engine instance. + * + * @param listener + * the {@link InterpreterEngineLifecycleListener} that should not listen to lifecycle events from this + * engine anymore + */ + public abstract void removeLifecycleListener(InterpreterEngineLifecycleListener listener); + + /** + * Add an model update event listener to this engine instance (that fires its callbacks according to its + * notification level). + * + * @param listener + * the {@link InterpreterModelUpdateListener} that should listen to model update events from this engine. + */ + public abstract void addModelUpdateListener(InterpreterModelUpdateListener listener); + + /** + * Remove an existing model update event listener to this engine instance. + * + * @param listener + * the {@link InterpreterModelUpdateListener} that should not listen to model update events from this engine + * anymore + */ + public abstract void removeModelUpdateListener(InterpreterModelUpdateListener 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 InterpreterMatcher#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 InterpreterMatcher} for which this listener should be active + */ + public abstract void addMatchUpdateListener(InterpreterMatcher matcher, + IMatchUpdateListener listener, boolean fireNow); + + /** + * Remove an existing match update event listener to this engine instance. + * + * @param matcher + * the {@link InterpreterMatcher} 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(InterpreterMatcher 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 Refinery Interpreter 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 InterpreterRuntimeException 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 Refinery interpreter queries + * @param optionalEvaluationHints additional / overriding options on query evaluation; passing null means default options associated with each query + * @throws InterpreterRuntimeException + * 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 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 InterpreterEngineLifecycleListener} 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(InterpreterEngineLifecycleListener)} 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(InterpreterEngineLifecycleListener)} 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 Refinery Interpreter engine. + * @noreference for internal use only + * @throws InterpreterRuntimeException + */ + 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 Refinery Interpreter 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 InterpreterEngineOptions} of the engine. + * + * @return the engine options + * @since 1.4 + */ + public abstract InterpreterEngineOptions 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(InterpreterMatcher 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/interpreter/src/main/java/tools/refinery/interpreter/api/GenericPatternMatch.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/GenericPatternMatch.java new file mode 100644 index 00000000..7d8aa0c2 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.api; + +import java.util.Arrays; + +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/api/GenericPatternMatcher.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/GenericPatternMatcher.java new file mode 100644 index 00000000..63e09900 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/GenericPatternMatcher.java @@ -0,0 +1,84 @@ +/******************************************************************************* + * 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.interpreter.api; + +import tools.refinery.interpreter.api.impl.BaseMatcher; +import tools.refinery.interpreter.api.scope.QueryScope; +import tools.refinery.interpreter.matchers.tuple.Tuple; + +/** + * This is a generic pattern matcher for any Refinery Interpreter 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(InterpreterEngine)}. + * in conjunction with {@link InterpreterEngine#on(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(InterpreterEngine 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/interpreter/src/main/java/tools/refinery/interpreter/api/GenericQueryGroup.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/GenericQueryGroup.java new file mode 100644 index 00000000..8be570c5 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/api/GenericQuerySpecification.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/GenericQuerySpecification.java new file mode 100644 index 00000000..47502a8c --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/GenericQuerySpecification.java @@ -0,0 +1,75 @@ +/******************************************************************************* + * 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.interpreter.api; + +import tools.refinery.interpreter.api.impl.BaseQuerySpecification; +import tools.refinery.interpreter.matchers.InterpreterRuntimeException; +import tools.refinery.interpreter.matchers.psystem.queries.PQuery; +import tools.refinery.interpreter.matchers.psystem.queries.PVisibility; + +/** + * This is a generic query specification for Refinery Interpreter 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(InterpreterEngine)} for implementing + * {@link #instantiate(InterpreterEngine)} if they use {@link GenericPatternMatcher} proper. + * + * @see GenericPatternMatcher + * @see GenericPatternMatch + * @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(InterpreterEngine)} + * if they use {@link GenericPatternMatcher} proper. + * @throws InterpreterRuntimeException + */ + protected GenericPatternMatcher defaultInstantiate(InterpreterEngine engine) { + return GenericPatternMatcher.instantiate(engine, this); + } + + /** + * @since 2.0 + */ + @Override + public PVisibility getVisibility() { + return getInternalQueryRepresentation().getVisibility(); + } + +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/IMatchUpdateListener.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/IMatchUpdateListener.java new file mode 100644 index 00000000..3d71f947 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.api; + +/** + * An interface for low-level notifications about match appearance and disappearance. + * + *

+ * See {@link InterpreterMatcher#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/interpreter/src/main/java/tools/refinery/interpreter/api/IPatternMatch.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/IPatternMatch.java new file mode 100644 index 00000000..6ea8e0db --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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 InterpreterMatcher#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: + *

+ * + *

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/interpreter/src/main/java/tools/refinery/interpreter/api/IQueryGroup.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/IQueryGroup.java new file mode 100644 index 00000000..151d0723 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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 InterpreterEngine}. 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 Refinery Interpreter engine in which the matchers will be created. + * @throws ViatraQueryRuntimeException + * if there was an error in preparing the engine + */ + public void prepare(InterpreterEngine engine); + + /** + * Returns the currently assigned {@link IQuerySpecification}s. + */ + public Set> getSpecifications(); + +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/IQuerySpecification.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/IQuerySpecification.java new file mode 100644 index 00000000..22861842 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/IQuerySpecification.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * 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.interpreter.api; + +import tools.refinery.interpreter.api.scope.QueryScope; +import tools.refinery.interpreter.matchers.InterpreterRuntimeException; +import tools.refinery.interpreter.matchers.psystem.queries.PQuery; +import tools.refinery.interpreter.matchers.psystem.queries.PQueryHeader; + +/** + * API interface for a Refinery Interpreter 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 InterpreterEngine}. 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 Refinery Interpreter engine in which this matcher will be created. + * @throws InterpreterRuntimeException + * if an error occurs during pattern matcher creation + */ + public Matcher getMatcher(InterpreterEngine 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 InterpreterEngine} instance to instantiate matchers. + * @throws InterpreterRuntimeException + * @noreference This method is not intended to be referenced by clients. + * @since 1.4 + */ + public Matcher instantiate(); + +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/InterpreterEngine.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/InterpreterEngine.java new file mode 100644 index 00000000..e4330783 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/InterpreterEngine.java @@ -0,0 +1,107 @@ +/******************************************************************************* + * 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.interpreter.api; + +import tools.refinery.interpreter.api.scope.IBaseIndex; +import tools.refinery.interpreter.api.scope.QueryScope; +import tools.refinery.interpreter.matchers.InterpreterRuntimeException; + +import java.util.Set; +import java.util.function.Supplier; +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 AdvancedInterpreterEngine} if you want fine control over the lifecycle of an engine. + * + *

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

+ * 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 InterpreterEngine { + + /** + * 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(InterpreterEngine)} instead to access EMF-specific details. + * + * @return the baseIndex the NavigationHelper maintaining the base index + * @throws InterpreterRuntimeException + * 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 Refinery Interpreter query specification + * @return a pattern matcher corresponding to the specification + * @throws InterpreterRuntimeException 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 Refinery Interpreter query specification + * @return a pattern matcher corresponding to the specification + * @throws InterpreterRuntimeException if the matcher could not be initialized + */ + public abstract InterpreterMatcher getMatcher(String patternFQN); + + /** + * Access an existing pattern matcher based on a {@link IQuerySpecification}. + * @param querySpecification a {@link IQuerySpecification} that describes a Refinery Interpreter 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 InterpreterMatcher} 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(InterpreterMatcher::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(); + + public abstract T withFlushingChanges(Supplier supplier); +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/InterpreterEngineInitializationListener.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/InterpreterEngineInitializationListener.java new file mode 100644 index 00000000..f26c1bb3 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/InterpreterEngineInitializationListener.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.interpreter.api; + +/** + * Listener interface to get notifications when a new managed engine is initialized. + * + * @author Abel Hegedus + * + */ +public interface InterpreterEngineInitializationListener { + + /** + * Called when a managed engine is initialized in the EngineManager. + * + * @param engine the initialized engine + */ + void engineInitialized(AdvancedInterpreterEngine engine); + +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/InterpreterEngineLifecycleListener.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/InterpreterEngineLifecycleListener.java new file mode 100644 index 00000000..c9c16c01 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/InterpreterEngineLifecycleListener.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.interpreter.api; + + +/** + * Listener interface for getting notification on changes in an {@link InterpreterEngine}. + * + * 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 InterpreterEngineLifecycleListener { + + // ------------------------------------------------------------------------------- + // 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(InterpreterMatcher 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/interpreter/src/main/java/tools/refinery/interpreter/api/InterpreterEngineOptions.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/InterpreterEngineOptions.java new file mode 100644 index 00000000..2b0f949b --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/InterpreterEngineOptions.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.interpreter.api; + + +import tools.refinery.interpreter.matchers.backend.IQueryBackendFactory; +import tools.refinery.interpreter.matchers.backend.IQueryBackendFactoryProvider; +import tools.refinery.interpreter.matchers.backend.QueryEvaluationHint; +import tools.refinery.interpreter.matchers.util.Preconditions; + +import java.util.Objects; +import java.util.ServiceLoader; + +/** + * This class is intended to provide options to a created {@link InterpreterEngine} 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(InterpreterEngineOptions)} (starts with all options from an existing configuration). + * + * @author Balázs Grill, Zoltan Ujhelyi + * @since 1.4 + * + */ +public final class InterpreterEngineOptions { + + 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; + InterpreterEngineOptions.systemDefaultBackendFactory = systemDefaultBackendFactory; + InterpreterEngineOptions.systemDefaultCachingBackendFactory = systemDefaultCachingBackendFactory; + InterpreterEngineOptions.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 InterpreterEngineOptions DEFAULT; + + /** + * @since 2.0 + */ + public static final InterpreterEngineOptions 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(InterpreterEngineOptions 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 InterpreterEngineOptions build() { + IQueryBackendFactory defaultFactory = getDefaultBackend(); + QueryEvaluationHint hint = getEngineDefaultHints(defaultFactory); + return new InterpreterEngineOptions(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(InterpreterEngineOptions options) { + return new Builder(options); + } + + private InterpreterEngineOptions(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 InterpreterEngineOptions.getSystemDefaultCachingBackend(); + case DEFAULT_SEARCH: + return InterpreterEngineOptions.getSystemDefaultCachingBackend(); + case SPECIFIC: + return engineDefaultHints.getQueryBackendFactory(); + case UNSPECIFIED: + default: + return InterpreterEngineOptions.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/interpreter/src/main/java/tools/refinery/interpreter/api/InterpreterMatcher.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/InterpreterMatcher.java new file mode 100644 index 00000000..beeb124c --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/InterpreterMatcher.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.interpreter.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 Refinery Interpreter 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 InterpreterMatcher { + // 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 + */ + InterpreterEngine getEngine(); +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/InterpreterModelUpdateListener.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/InterpreterModelUpdateListener.java new file mode 100644 index 00000000..796b1828 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/InterpreterModelUpdateListener.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.interpreter.api; + + + +/** + * Listener interface for model changes affecting different levels of the Refinery Interpreter architecture. + * + * @author Abel Hegedus + * + */ +public interface InterpreterModelUpdateListener { + + /** + * 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/interpreter/src/main/java/tools/refinery/interpreter/api/MatchUpdateAdapter.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/MatchUpdateAdapter.java new file mode 100644 index 00000000..7b3a2087 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/api/impl/BaseGeneratedPatternGroup.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/impl/BaseGeneratedPatternGroup.java new file mode 100644 index 00000000..be8fc07c --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.api.impl; + +import java.util.HashSet; +import java.util.Set; + +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/api/impl/BaseMatcher.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/impl/BaseMatcher.java new file mode 100644 index 00000000..7cf36736 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.internal.apiimpl.QueryResultWrapper; +import tools.refinery.interpreter.matchers.backend.IMatcherCapability; +import tools.refinery.interpreter.matchers.backend.IQueryResultProvider; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.util.Preconditions; +import tools.refinery.interpreter.api.IPatternMatch; +import tools.refinery.interpreter.api.IQuerySpecification; +import tools.refinery.interpreter.api.InterpreterEngine; +import tools.refinery.interpreter.api.InterpreterMatcher; + +/** + * Base implementation of ViatraQueryMatcher. + * + * @author Bergmann Gábor + * + * @param + */ +public abstract class BaseMatcher extends QueryResultWrapper implements InterpreterMatcher { + + // FIELDS AND CONSTRUCTOR + + protected InterpreterEngine 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(InterpreterEngine 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 InterpreterEngine 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/interpreter/src/main/java/tools/refinery/interpreter/api/impl/BasePatternMatch.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/impl/BasePatternMatch.java new file mode 100644 index 00000000..0dfaa666 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/impl/BasePatternMatch.java @@ -0,0 +1,91 @@ +/******************************************************************************* + * 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.interpreter.api.impl; + +import tools.refinery.interpreter.api.IPatternMatch; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * 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)"; + } + return o.toString(); + } + + // 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/interpreter/src/main/java/tools/refinery/interpreter/api/impl/BaseQueryGroup.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/impl/BaseQueryGroup.java new file mode 100644 index 00000000..c0f619ea --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.api.impl; + +import tools.refinery.interpreter.api.AdvancedInterpreterEngine; +import tools.refinery.interpreter.api.IQueryGroup; +import tools.refinery.interpreter.api.InterpreterEngine; + +/** + * Base implementation of {@link IQueryGroup}. + * + * @author Mark Czotter + * + */ +public abstract class BaseQueryGroup implements IQueryGroup { + + @Override + public void prepare(InterpreterEngine engine) { + prepare(AdvancedInterpreterEngine.from(engine)); + } + + protected void prepare(AdvancedInterpreterEngine engine) { + engine.prepareGroup(this, null /* default options */); + } + + +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/impl/BaseQuerySpecification.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/impl/BaseQuerySpecification.java new file mode 100644 index 00000000..411a8c18 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/impl/BaseQuerySpecification.java @@ -0,0 +1,146 @@ +/******************************************************************************* + * 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.interpreter.api.impl; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import tools.refinery.interpreter.exception.InterpreterException; +import tools.refinery.interpreter.matchers.psystem.annotations.PAnnotation; +import tools.refinery.interpreter.matchers.psystem.queries.PParameter; +import tools.refinery.interpreter.matchers.psystem.queries.PQuery; +import tools.refinery.interpreter.matchers.psystem.queries.PVisibility; +import tools.refinery.interpreter.matchers.psystem.queries.QueryInitializationException; +import tools.refinery.interpreter.api.IPatternMatch; +import tools.refinery.interpreter.api.IQuerySpecification; +import tools.refinery.interpreter.api.InterpreterEngine; +import tools.refinery.interpreter.api.InterpreterMatcher; + +/** + * Base implementation of IQuerySpecification. + * + * @author Gabor Bergmann + * + */ +public abstract class BaseQuerySpecification> implements + IQuerySpecification { + + /** + * @since 1.6 + */ + protected static InterpreterException processInitializerError(ExceptionInInitializerError err) { + Throwable cause1 = err.getCause(); + if (cause1 instanceof RuntimeException) { + Throwable cause2 = ((RuntimeException) cause1).getCause(); + if (cause2 instanceof InterpreterException) { + return (InterpreterException) cause2; + } else if (cause2 instanceof QueryInitializationException) { + return new InterpreterException((QueryInitializationException) cause2); + } + } + throw err; + } + protected final PQuery wrappedPQuery; + + protected abstract Matcher instantiate(InterpreterEngine 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(InterpreterEngine engine) { + ensureInitializedInternal(); + if (wrappedPQuery.getStatus() == PQuery.PQueryStatus.ERROR) { + String errorMessages = wrappedPQuery.getPProblems().stream() + .map(input -> (input == null) ? "" : input.getShortMessage()).collect(Collectors.joining("\n")); + throw new InterpreterException(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 InterpreterException( + 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/interpreter/src/main/java/tools/refinery/interpreter/api/scope/IBaseIndex.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/scope/IBaseIndex.java new file mode 100644 index 00000000..949ab2d7 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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(InterpreterBaseIndexChangeListener)} + * @param listener + */ + public void addBaseIndexChangeListener(InterpreterBaseIndexChangeListener listener); + + /** + * Removes a registered listener. + * + *

See {@link #addBaseIndexChangeListener(InterpreterBaseIndexChangeListener)} + * + * @param listener + */ + public void removeBaseIndexChangeListener(InterpreterBaseIndexChangeListener 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/interpreter/src/main/java/tools/refinery/interpreter/api/scope/IEngineContext.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/scope/IEngineContext.java new file mode 100644 index 00000000..013a3d1c --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.api.scope; + +import tools.refinery.interpreter.matchers.InterpreterRuntimeException; +import tools.refinery.interpreter.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 InterpreterRuntimeException 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 InterpreterRuntimeException if the runtime context cannot be initialized + */ + public IQueryRuntimeContext getQueryRuntimeContext(); +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/scope/IIndexingErrorListener.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/scope/IIndexingErrorListener.java new file mode 100644 index 00000000..904619b2 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/scope/IIndexingErrorListener.java @@ -0,0 +1,23 @@ +/******************************************************************************* + * 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.interpreter.api.scope; + +/** + * + * 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/interpreter/src/main/java/tools/refinery/interpreter/api/scope/IInstanceObserver.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/scope/IInstanceObserver.java new file mode 100644 index 00000000..18c43aac --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/api/scope/InterpreterBaseIndexChangeListener.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/scope/InterpreterBaseIndexChangeListener.java new file mode 100644 index 00000000..97a2166f --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/scope/InterpreterBaseIndexChangeListener.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * 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.interpreter.api.scope; + +/** + * Listener interface for change notifications from the Refinery Base index. + * + * @author Abel Hegedus + * @since 0.9 + * + */ +public interface InterpreterBaseIndexChangeListener { + + /** + * 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 Refinery Interpreter 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/interpreter/src/main/java/tools/refinery/interpreter/api/scope/QueryScope.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/scope/QueryScope.java new file mode 100644 index 00000000..81e200d7 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/api/scope/QueryScope.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * 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.interpreter.api.scope; + +import tools.refinery.interpreter.internal.apiimpl.EngineContextFactory; +import tools.refinery.interpreter.api.IQuerySpecification; +import tools.refinery.interpreter.api.InterpreterEngine; + +/** + * Defines a scope for a Refinery Interpreter 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 InterpreterEngine} initialized on this scope can consume an {@link IQuerySpecification} + */ + public boolean isCompatibleWithQueryScope(Class queryScopeClass) { + return queryScopeClass.isAssignableFrom(this.getClass()); + } + +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/exception/InterpreterException.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/exception/InterpreterException.java new file mode 100644 index 00000000..2100f11e --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/exception/InterpreterException.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * 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.interpreter.exception; + +import tools.refinery.interpreter.matchers.InterpreterRuntimeException; +import tools.refinery.interpreter.matchers.planning.QueryProcessingException; +import tools.refinery.interpreter.matchers.psystem.queries.QueryInitializationException; + +import java.io.Serial; + +/** + * A general Refinery Interpreter-related problem during the operation of the Refinery Interpreter + * engine, or the loading, manipulation and evaluation of queries. + * + * @author Bergmann Gabor + * @since 0.9 + * + */ +public class InterpreterException extends InterpreterRuntimeException { + + @Serial + private static final long serialVersionUID = -74252748358355750L; + + /** + * @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 Refinery pattern matcher)"; + /** + * @since 0.9 + */ + public static final String QUERY_INIT_PROBLEM = "The following error occurred during the initialization of a " + + "Refinery query specification"; + + private final String shortMessage; + + public InterpreterException(String s, String shortMessage) { + super(s); + this.shortMessage = shortMessage; + } + + public InterpreterException(QueryProcessingException e) { + super(PROCESSING_PROBLEM + ": " + e.getMessage(), e); + this.shortMessage = e.getShortMessage(); + } + + public InterpreterException(QueryInitializationException e) { + super(QUERY_INIT_PROBLEM + ": " + e.getMessage(), e); + this.shortMessage = e.getShortMessage(); + } + + public InterpreterException(String s, String shortMessage, Throwable e) { + super(s + ": " + e.getMessage(), e); + this.shortMessage = shortMessage; + } + + public String getShortMessage() { + return shortMessage; + } + +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/internal/apiimpl/EngineContextFactory.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/internal/apiimpl/EngineContextFactory.java new file mode 100644 index 00000000..9e9172cb --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.internal.apiimpl; + +import org.apache.log4j.Logger; +import tools.refinery.interpreter.api.InterpreterEngine; +import tools.refinery.interpreter.api.scope.IEngineContext; +import tools.refinery.interpreter.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(InterpreterEngine engine, IIndexingErrorListener errorListener, Logger logger); +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/internal/apiimpl/InterpreterEngineImpl.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/internal/apiimpl/InterpreterEngineImpl.java new file mode 100644 index 00000000..8a2e4c5e --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/internal/apiimpl/InterpreterEngineImpl.java @@ -0,0 +1,689 @@ +/******************************************************************************* + * 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.interpreter.internal.apiimpl; + +import org.apache.log4j.Logger; +import tools.refinery.interpreter.api.*; +import tools.refinery.interpreter.api.impl.BaseMatcher; +import tools.refinery.interpreter.api.scope.IBaseIndex; +import tools.refinery.interpreter.api.scope.IEngineContext; +import tools.refinery.interpreter.api.scope.IIndexingErrorListener; +import tools.refinery.interpreter.api.scope.QueryScope; +import tools.refinery.interpreter.exception.InterpreterException; +import tools.refinery.interpreter.internal.engine.LifecycleProvider; +import tools.refinery.interpreter.internal.engine.ModelUpdateProvider; +import tools.refinery.interpreter.matchers.InterpreterRuntimeException; +import tools.refinery.interpreter.matchers.backend.*; +import tools.refinery.interpreter.matchers.context.IQueryBackendContext; +import tools.refinery.interpreter.matchers.context.IQueryCacheContext; +import tools.refinery.interpreter.matchers.context.IQueryResultProviderAccess; +import tools.refinery.interpreter.matchers.context.IQueryRuntimeContext; +import tools.refinery.interpreter.matchers.planning.QueryProcessingException; +import tools.refinery.interpreter.matchers.psystem.analysis.QueryAnalyzer; +import tools.refinery.interpreter.matchers.psystem.queries.PQueries; +import tools.refinery.interpreter.matchers.psystem.queries.PQuery; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.matchers.util.IMultiLookup; +import tools.refinery.interpreter.matchers.util.Preconditions; +import tools.refinery.interpreter.util.InterpreterLoggingUtil; + +import java.lang.ref.WeakReference; +import java.lang.reflect.InvocationTargetException; +import java.util.*; +import java.util.concurrent.Callable; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +/** + * A Refinery Interpreter engine back-end (implementation) + * + * @author Bergmann Gábor + */ +public final class InterpreterEngineImpl extends AdvancedInterpreterEngine + 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 model to which the engine is attached. + */ + private final QueryScope scope; + + /** + * The context of the engine, provided by the scope. + */ + private final IEngineContext engineContext; + + /** + * Initialized matchers for each query + */ + private final IMultiLookup>, InterpreterMatcher> matchers = + CollectionsFactory.createMultiLookup(Object.class, CollectionsFactory.MemoryType.SETS, Object.class); + + /** + * The RETE and other pattern matcher implementations of the Refinery Interpreter engine. + */ + private final Map queryBackends = Collections.synchronizedMap(new HashMap<>()); + + /** + * The current engine default hints + */ + private final InterpreterEngineOptions 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 scope + * @param engineDefaultHint + * @since 1.4 + */ + public InterpreterEngineImpl(QueryScope scope, + InterpreterEngineOptions engineOptions) { + super(); + 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 = InterpreterEngineOptions.getDefault(); + } + + } + + /** + * @param manager + * null if unmanaged + * @param scope + * @param engineDefaultHint + */ + public InterpreterEngineImpl(QueryScope scope) { + this(scope, InterpreterEngineOptions.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 { + flushAllBackends(); + } finally { + delayMessageDelivery = true; + } + } + + private void flushAllBackends() { + for (IQueryBackend backend : this.queryBackends.values()) { + backend.flushUpdates(); + } + } + + @Override + public T withFlushingChanges(Supplier callback) { + if (!delayMessageDelivery) { + return callback.get(); + } + delayMessageDelivery = false; + try { + flushAllBackends(); + return callback.get(); + } 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) { + return withFlushingChanges(() -> { + 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 (InterpreterMatcher matcher : matchers.lookupOrEmpty(querySpecification)) { + BaseMatcher baseMatcher = (BaseMatcher) matcher; + if (baseMatcher.getCapabilities().canBeSubstitute(requestedCapability)) + return (Matcher) matcher; + } + return null; + } + + @Override + public InterpreterMatcher getMatcher(String patternFQN) { + throw new UnsupportedOperationException("Query specification registry is not available"); + } + + @Override + public IBaseIndex getBaseIndex() { + return engineContext.getBaseIndex(); + } + + public final Logger getLogger() { + if (logger == null) { + final int hash = System.identityHashCode(this); + logger = Logger.getLogger(InterpreterLoggingUtil.getLogger(InterpreterEngine.class).getName() + "." + hash); + if (logger == null) + throw new AssertionError( + "Configuration error: unable to create Refinery Interpreter runtime logger for engine " + hash); + } + return logger; + } + + ///////////////// internal stuff ////////////// + private void internalRegisterMatcher(IQuerySpecification querySpecification, InterpreterMatcher matcher) { + matchers.addPair(querySpecification, matcher); + lifecycleProvider.matcherInstantiated(matcher); + } + + /** + * Provides access to the selected query backend component of the Refinery Interpreter 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 InterpreterEngineImpl.this; + } + + @Override + public Logger getLogger() { + return logger; + } + + @Override + public IQueryBackendHintProvider getHintProvider() { + return InterpreterEngineImpl.this; + } + + @Override + public IQueryResultProviderAccess getResultProviderAccess() { + return InterpreterEngineImpl.this; + } + + @Override + public QueryAnalyzer getQueryAnalyzer() { + if (queryAnalyzer == null) + queryAnalyzer = new QueryAnalyzer(queryRuntimeContext.getMetaContext()); + return queryAnalyzer; + } + + @Override + public boolean areUpdatesDelayed() { + return InterpreterEngineImpl.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() { + 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 Refinery Interpreter engine, as there are " + + "still active listeners on it."); + } + } + + @Override + public void wipe() { + 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(InterpreterEngineImpl queryEngine) { + this.queryEngineRef = new WeakReference(queryEngine); + } + + public void engineBecameTainted(String description, Throwable t) { + final InterpreterEngineImpl 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; + } + + private IQueryResultProvider getUnderlyingResultProvider( + final BaseMatcher matcher) { + // IQueryResultProvider resultProvider = reteEngine.accessMatcher(matcher.getSpecification()); + return matcher.backend; + } + + @Override + public void addMatchUpdateListener(final InterpreterMatcher matcher, + final IMatchUpdateListener listener, boolean fireNow) { + + Preconditions.checkArgument(listener != null, "Cannot add null listener!"); + Preconditions.checkArgument(matcher.getEngine() == this, "Cannot register listener for matcher of different engine!"); + Preconditions.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(InterpreterMatcher matcher, + IMatchUpdateListener listener) { + Preconditions.checkArgument(listener != null, "Cannot remove null listener!"); + Preconditions.checkArgument(matcher.getEngine() == this, "Cannot remove listener from matcher of different engine!"); + Preconditions.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(InterpreterModelUpdateListener listener) { + modelUpdateProvider.addListener(listener); + } + + @Override + public void removeModelUpdateListener(InterpreterModelUpdateListener listener) { + modelUpdateProvider.removeListener(listener); + } + + @Override + public void addLifecycleListener(InterpreterEngineLifecycleListener listener) { + lifecycleProvider.addListener(listener); + } + + @Override + public void removeLifecycleListener(InterpreterEngineLifecycleListener 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 InterpreterRuntimeException + */ + 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 InterpreterRuntimeException + */ + 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 InterpreterRuntimeException + */ + 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 InterpreterRuntimeException + */ + private IQueryResultProvider getResultProviderInternal(PQuery query, QueryEvaluationHint hint) { + Preconditions.checkArgument(query != null, "Query cannot be null!"); + Preconditions.checkArgument(query.getStatus() != PQuery.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 InterpreterRuntimeException + */ + 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 InterpreterRuntimeException + */ + 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 (InterpreterException iqe) { + getLogger().error(ERROR_ACCESSING_BACKEND, iqe); + return false; + } + } + + @Override + public IQueryResultProvider getCachingResultProvider(PQuery query) { + try { + return getCachingQueryBackend(query).getResultProvider(query); + } catch (InterpreterException 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(PQuery.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 InterpreterException) + throw (InterpreterException) cause; + if (cause instanceof RuntimeException) + throw (RuntimeException) cause; + assert (false); + } + } catch (QueryProcessingException e) { + throw new InterpreterException(e); + } + } + + @Override + public QueryScope getScope() { + return scope; + } + + @Override + public InterpreterEngineOptions getEngineOptions() { + return engineOptions; + } + + @Override + public IQueryResultProvider getResultProviderOfMatcher(InterpreterMatcher matcher) { + return ((QueryResultWrapper) matcher).backend; + } + + @Override + public IQueryResultProvider getResultProvider(PQuery query, QueryEvaluationHint overrideHints) { + try { + return getResultProviderInternal(query, overrideHints); + } catch (InterpreterException e) { + getLogger().error(ERROR_ACCESSING_BACKEND, e); + throw e; + } + } + + @Override + public boolean isDisposed() { + return disposed; + } + +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/internal/apiimpl/QueryResultWrapper.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/internal/apiimpl/QueryResultWrapper.java new file mode 100644 index 00000000..0f50aee1 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.internal.apiimpl; + +import tools.refinery.interpreter.matchers.backend.IMatcherCapability; +import tools.refinery.interpreter.matchers.backend.IQueryResultProvider; +import tools.refinery.interpreter.api.InterpreterEngine; + +/** + * Internal class for wrapping a query result providing backend. It's only supported usage is by the + * {@link InterpreterEngineImpl} 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(InterpreterEngine engine, IQueryResultProvider resultProvider, IMatcherCapability capabilities); + +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/internal/engine/LifecycleProvider.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/internal/engine/LifecycleProvider.java new file mode 100644 index 00000000..2e89e832 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.internal.engine; + +import org.apache.log4j.Logger; +import tools.refinery.interpreter.api.AdvancedInterpreterEngine; +import tools.refinery.interpreter.api.IPatternMatch; +import tools.refinery.interpreter.api.InterpreterEngineLifecycleListener; +import tools.refinery.interpreter.api.InterpreterMatcher; + +import java.util.ArrayList; + +public final class LifecycleProvider extends ListenerContainer implements InterpreterEngineLifecycleListener { + + private final Logger logger; + + /** + * @param queryEngine + */ + public LifecycleProvider(AdvancedInterpreterEngine queryEngine, Logger logger) { + this.logger = logger; + } + + @Override + protected void listenerAdded(InterpreterEngineLifecycleListener listener) { + logger.debug("Lifecycle listener " + listener + " added to engine."); + } + + @Override + protected void listenerRemoved(InterpreterEngineLifecycleListener 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( +// "Refinery Interpreter encountered an error in delivering notification to listener " +// + listener + ".", ex); +// } +// } +// } +// } + + @Override + public void matcherInstantiated(final InterpreterMatcher matcher) { + if (!listeners.isEmpty()) { + for (InterpreterEngineLifecycleListener listener : new ArrayList(listeners)) { + try { + listener.matcherInstantiated(matcher); + } catch (Exception ex) { + logger.error( + "Refinery Interpreter 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 (InterpreterEngineLifecycleListener listener : new ArrayList(listeners)) { + try { + listener.engineBecameTainted(description, t); + } catch (Exception ex) { + logger.error( + "Refinery Interpreter 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 (InterpreterEngineLifecycleListener listener : new ArrayList(listeners)) { + try { + listener.engineWiped(); + } catch (Exception ex) { + logger.error( + "Refinery Interpreter 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 (InterpreterEngineLifecycleListener listener : new ArrayList(listeners)) { + try { + listener.engineDisposed(); + } catch (Exception ex) { + logger.error( + "Refinery Interpreter encountered an error in delivering engine disposed notification" + + " to listener " + listener + ".", ex); + } + } + } +// propagateEventToListeners(new Predicate() { +// public boolean apply(ViatraQueryEngineLifecycleListener listener) { +// listener.engineDisposed(); +// return true; +// } +// }); + } + + } diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/internal/engine/ListenerContainer.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/internal/engine/ListenerContainer.java new file mode 100644 index 00000000..d168e4f6 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/internal/engine/ListenerContainer.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * 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.interpreter.internal.engine; + +import tools.refinery.interpreter.matchers.util.Preconditions; + +import static tools.refinery.interpreter.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) { + Preconditions.checkArgument(listener != null, "Cannot add null listener!"); + boolean added = listeners.add(listener); + if(added) { + listenerAdded(listener); + } + } + + public synchronized void removeListener(Listener listener) { + Preconditions.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); +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/internal/engine/ModelUpdateProvider.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/internal/engine/ModelUpdateProvider.java new file mode 100644 index 00000000..637ee306 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/internal/engine/ModelUpdateProvider.java @@ -0,0 +1,204 @@ +/******************************************************************************* + * 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.interpreter.internal.engine; + +import org.apache.log4j.Logger; +import tools.refinery.interpreter.api.*; +import tools.refinery.interpreter.api.InterpreterModelUpdateListener.ChangeLevel; +import tools.refinery.interpreter.api.scope.InterpreterBaseIndexChangeListener; +import tools.refinery.interpreter.exception.InterpreterException; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; + +import java.util.*; +import java.util.Map.Entry; + +public final class ModelUpdateProvider extends ListenerContainer { + + private final AdvancedInterpreterEngine queryEngine; + private ChangeLevel currentChange = ChangeLevel.NO_CHANGE; + private ChangeLevel maxLevel = ChangeLevel.NO_CHANGE; + private final Map> listenerMap; + private final Logger logger; + + public ModelUpdateProvider(AdvancedInterpreterEngine queryEngine, Logger logger) { + super(); + this.queryEngine = queryEngine; + this.logger = logger; + listenerMap = new EnumMap<>(ChangeLevel.class); + } + + @Override + protected void listenerAdded(InterpreterModelUpdateListener 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 (InterpreterException 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 (InterpreterMatcher matcher : this.queryEngine.getCurrentMatchers()) { + this.queryEngine.addMatchUpdateListener(matcher, matchSetListener, false); + } + } + } + + @Override + protected void listenerRemoved(InterpreterModelUpdateListener 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 (InterpreterException 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 (InterpreterMatcher matcher : this.queryEngine.getCurrentMatchers()) { + this.queryEngine.removeMatchUpdateListener(matcher, matchSetListener); + } + } + } + + private void handleUnsuccesfulRemove(InterpreterModelUpdateListener 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 (InterpreterModelUpdateListener listener : new ArrayList<>(listenerMap.get(level))) { + try { + listener.notifyChanged(tempLevel); + } catch (Exception ex) { + logger.error( + "Refinery Interpreter 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 InterpreterBaseIndexChangeListener indexListener = new InterpreterBaseIndexChangeListener() { + + @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 InterpreterEngineLifecycleListener selfListener = new InterpreterEngineLifecycleListener() { + + @Override + public void matcherInstantiated(InterpreterMatcher 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/interpreter/src/main/java/tools/refinery/interpreter/matchers/InterpreterRuntimeException.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/InterpreterRuntimeException.java new file mode 100644 index 00000000..3adbd4b6 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/InterpreterRuntimeException.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * 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.interpreter.matchers; + +import java.io.Serial; + +/** + * A common base class for all exceptions thrown by various Refinery Interpreter APIs. + * + * @author Zoltan Ujhelyi + * @since 2.0 + */ +public abstract class InterpreterRuntimeException extends RuntimeException { + + @Serial + private static final long serialVersionUID = -8505253058035069310L; + + public InterpreterRuntimeException() { + super(); + } + + public InterpreterRuntimeException(String message) { + super(message); + } + + public InterpreterRuntimeException(Throwable cause) { + super(cause); + } + + public InterpreterRuntimeException(String message, Throwable cause) { + super(message, cause); + } + + public InterpreterRuntimeException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/AverageAccumulator.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/AverageAccumulator.java new file mode 100644 index 00000000..86cfac23 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.aggregators; + +/** + * @since 2.0 + */ +class AverageAccumulator { + Domain value; + long count; + + public AverageAccumulator(Domain value, long count) { + super(); + this.value = value; + this.count = count; + } + +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/DoubleAverageOperator.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/DoubleAverageOperator.java new file mode 100644 index 00000000..30a79ff0 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.aggregators; + +import java.util.OptionalDouble; +import java.util.stream.Stream; + +import tools.refinery.interpreter.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); + } + +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/DoubleSumOperator.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/DoubleSumOperator.java new file mode 100644 index 00000000..a15aadd3 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.aggregators; + +import java.util.stream.Stream; + +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/ExtremumOperator.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/ExtremumOperator.java new file mode 100644 index 00000000..f5952b72 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.aggregators; + +import java.util.Comparator; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.stream.Stream; + +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/IntegerAverageOperator.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/IntegerAverageOperator.java new file mode 100644 index 00000000..c33fc382 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.aggregators; + +import java.util.OptionalDouble; +import java.util.stream.Stream; + +import tools.refinery.interpreter.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); + } + +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/IntegerSumOperator.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/IntegerSumOperator.java new file mode 100644 index 00000000..430875bc --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.aggregators; + +import java.util.stream.Stream; + +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/LongAverageOperator.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/LongAverageOperator.java new file mode 100644 index 00000000..1314adc3 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.aggregators; + +import java.util.OptionalDouble; +import java.util.stream.Stream; + +import tools.refinery.interpreter.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); + } + +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/LongSumOperator.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/LongSumOperator.java new file mode 100644 index 00000000..2d466314 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.aggregators; + +import java.util.stream.Stream; + +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/avg.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/avg.java new file mode 100644 index 00000000..0d0449d2 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.aggregators; + +import tools.refinery.interpreter.matchers.psystem.aggregations.AggregatorType; +import tools.refinery.interpreter.matchers.psystem.aggregations.BoundAggregator; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/count.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/count.java new file mode 100644 index 00000000..2cd5e7ed --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.aggregators; + +import tools.refinery.interpreter.matchers.psystem.aggregations.AggregatorType; +import tools.refinery.interpreter.matchers.psystem.aggregations.BoundAggregator; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/max.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/max.java new file mode 100644 index 00000000..b005bd10 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.aggregators; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Calendar; +import java.util.Date; + +import tools.refinery.interpreter.matchers.psystem.aggregations.AggregatorType; +import tools.refinery.interpreter.matchers.psystem.aggregations.BoundAggregator; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/min.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/min.java new file mode 100644 index 00000000..58d92e92 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.aggregators; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Calendar; +import java.util.Date; + +import tools.refinery.interpreter.matchers.psystem.aggregations.AggregatorType; +import tools.refinery.interpreter.matchers.psystem.aggregations.BoundAggregator; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/sum.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/aggregators/sum.java new file mode 100644 index 00000000..e60b21ee --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.aggregators; + +import tools.refinery.interpreter.matchers.psystem.aggregations.AggregatorType; +import tools.refinery.interpreter.matchers.psystem.aggregations.BoundAggregator; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/algorithms/OrderedIterableMerge.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/algorithms/OrderedIterableMerge.java new file mode 100644 index 00000000..57aff73d --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/algorithms/UnionFind.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/algorithms/UnionFind.java new file mode 100644 index 00000000..9d0f5275 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/algorithms/UnionFindNodeProperty.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/algorithms/UnionFindNodeProperty.java new file mode 100644 index 00000000..7b8ad3f7 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/CommonQueryHintOptions.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/CommonQueryHintOptions.java new file mode 100644 index 00000000..d7d4412e --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.backend; + +import tools.refinery.interpreter.matchers.psystem.rewriters.IRewriterTraceCollector; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/ICallDelegationStrategy.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/ICallDelegationStrategy.java new file mode 100644 index 00000000..f7f02c75 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.backend; + +import tools.refinery.interpreter.matchers.context.IQueryResultProviderAccess; +import tools.refinery.interpreter.matchers.psystem.IQueryReference; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/IMatcherCapability.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/IMatcherCapability.java new file mode 100644 index 00000000..ba2f4b42 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/IQueryBackend.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/IQueryBackend.java new file mode 100644 index 00000000..c98378a1 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/IQueryBackend.java @@ -0,0 +1,69 @@ +/******************************************************************************* + * 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.interpreter.matchers.backend; + +import tools.refinery.interpreter.matchers.psystem.queries.PQuery; +import tools.refinery.interpreter.matchers.InterpreterRuntimeException; + +/** + * Internal interface for a Refienry Interpreter 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 Refinery Interpreter 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 InterpreterRuntimeException + */ + 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 InterpreterRuntimeException + * @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/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/IQueryBackendFactory.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/IQueryBackendFactory.java new file mode 100644 index 00000000..dc82b9ce --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/IQueryBackendFactory.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * 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.interpreter.matchers.backend; + +import tools.refinery.interpreter.matchers.context.IQueryBackendContext; +import tools.refinery.interpreter.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 Refinery Interpreter 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/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/IQueryBackendFactoryProvider.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/IQueryBackendFactoryProvider.java new file mode 100644 index 00000000..c868fe5a --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/IQueryBackendHintProvider.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/IQueryBackendHintProvider.java new file mode 100644 index 00000000..992024a2 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.backend; + +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/IQueryResultProvider.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/IQueryResultProvider.java new file mode 100644 index 00000000..9183bc70 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.backend; + +import java.util.Optional; +import java.util.stream.Stream; + +import tools.refinery.interpreter.matchers.planning.helpers.StatisticsHelper; +import tools.refinery.interpreter.matchers.tuple.ITuple; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/IUpdateable.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/IUpdateable.java new file mode 100644 index 00000000..e46f4e32 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.backend; + +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/QueryEvaluationHint.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/QueryEvaluationHint.java new file mode 100644 index 00000000..188aaf62 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/QueryEvaluationHint.java @@ -0,0 +1,238 @@ +/******************************************************************************* + * 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.interpreter.matchers.backend; + +import tools.refinery.interpreter.matchers.util.Preconditions; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * Provides Refinery Interpreter 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/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/QueryHintOption.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/QueryHintOption.java new file mode 100644 index 00000000..516fa9a9 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/ResultProviderRequestor.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/backend/ResultProviderRequestor.java new file mode 100644 index 00000000..5fe57d5b --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.backend; + +import tools.refinery.interpreter.matchers.context.IQueryResultProviderAccess; +import tools.refinery.interpreter.matchers.psystem.IQueryReference; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/AbstractQueryMetaContext.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/AbstractQueryMetaContext.java new file mode 100644 index 00000000..14ca52c1 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/AbstractQueryRuntimeContext.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/AbstractQueryRuntimeContext.java new file mode 100644 index 00000000..fa6b5a26 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/IInputKey.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/IInputKey.java new file mode 100644 index 00000000..dd6680cb --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/IPosetComparator.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/IPosetComparator.java new file mode 100644 index 00000000..137e6338 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.context; + +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/IQueryBackendContext.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/IQueryBackendContext.java new file mode 100644 index 00000000..a2f60f0d --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.context; + +import org.apache.log4j.Logger; +import tools.refinery.interpreter.matchers.psystem.analysis.QueryAnalyzer; +import tools.refinery.interpreter.matchers.psystem.queries.PQuery; +import tools.refinery.interpreter.matchers.backend.IMatcherCapability; +import tools.refinery.interpreter.matchers.backend.IQueryBackendHintProvider; +import tools.refinery.interpreter.matchers.backend.QueryEvaluationHint; + +/** + * 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/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/IQueryCacheContext.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/IQueryCacheContext.java new file mode 100644 index 00000000..af525fae --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.context; + +import tools.refinery.interpreter.matchers.psystem.queries.PQuery; +import tools.refinery.interpreter.matchers.InterpreterRuntimeException; +import tools.refinery.interpreter.matchers.backend.IQueryBackend; +import tools.refinery.interpreter.matchers.backend.IQueryResultProvider; + +/** + * 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 InterpreterRuntimeException + */ + public IQueryResultProvider getCachingResultProvider(PQuery query); +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/IQueryMetaContext.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/IQueryMetaContext.java new file mode 100644 index 00000000..36e5ae95 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/IQueryResultProviderAccess.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/IQueryResultProviderAccess.java new file mode 100644 index 00000000..e4970cef --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.context; + +import tools.refinery.interpreter.matchers.psystem.queries.PQuery; +import tools.refinery.interpreter.matchers.backend.IQueryResultProvider; +import tools.refinery.interpreter.matchers.backend.QueryEvaluationHint; + +/** + * 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/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/IQueryRuntimeContext.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/IQueryRuntimeContext.java new file mode 100644 index 00000000..1a3b300a --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/IQueryRuntimeContext.java @@ -0,0 +1,287 @@ +/******************************************************************************* + * Copyright (c) 2010-2015, 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.interpreter.matchers.context; + +import tools.refinery.interpreter.matchers.planning.helpers.StatisticsHelper; +import tools.refinery.interpreter.CancellationToken; +import tools.refinery.interpreter.matchers.tuple.ITuple; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.util.Accuracy; + +import java.lang.reflect.InvocationTargetException; +import java.util.Optional; +import java.util.concurrent.Callable; + +/** + * 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; + + default CancellationToken getCancellationToken() { + return CancellationToken.NONE; + } +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/IQueryRuntimeContextListener.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/IQueryRuntimeContextListener.java new file mode 100644 index 00000000..87b07099 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.context; + +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/IndexingService.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/IndexingService.java new file mode 100644 index 00000000..08183746 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/IndexingService.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * 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.interpreter.matchers.context; + +import tools.refinery.interpreter.matchers.tuple.Tuple; + +/** + * 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, 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/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/InputKeyImplication.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/InputKeyImplication.java new file mode 100644 index 00000000..b98a8d2f --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/common/BaseInputKeyWrapper.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/common/BaseInputKeyWrapper.java new file mode 100644 index 00000000..127a8631 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.context.common; + +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/common/JavaTransitiveInstancesKey.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/common/JavaTransitiveInstancesKey.java new file mode 100644 index 00000000..fc1accbd --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/common/JavaTransitiveInstancesKey.java @@ -0,0 +1,174 @@ +/******************************************************************************* + * 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.interpreter.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(getName(instanceClass)); + 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(); + if (cachedWrapperInstanceClass == null) { + return wrappedKey == null ? "" : wrappedKey; + } + return 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; + } + + private static String getName(Class instanceClass) { + Class wrapperClass = primitiveTypeToWrapperClass(instanceClass); + if (wrapperClass == null) { + return ""; + } + return wrapperClass.getName(); + } +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/surrogate/SurrogateQueryRegistry.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/context/surrogate/SurrogateQueryRegistry.java new file mode 100644 index 00000000..83eba8c9 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.matchers.psystem.queries.PQuery; +import tools.refinery.interpreter.matchers.context.IInputKey; +import tools.refinery.interpreter.matchers.util.IProvider; +import tools.refinery.interpreter.matchers.util.Preconditions; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/memories/AbstractTrivialMaskedMemory.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/memories/AbstractTrivialMaskedMemory.java new file mode 100644 index 00000000..94572c06 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.memories; + +import java.util.Iterator; +import java.util.Map; + +import tools.refinery.interpreter.matchers.tuple.ITuple; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.matchers.util.CollectionsFactory.MemoryType; +import tools.refinery.interpreter.matchers.util.timeline.Timeline; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/memories/DefaultMaskedTupleMemory.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/memories/DefaultMaskedTupleMemory.java new file mode 100644 index 00000000..22c4140a --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.memories; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; + +import tools.refinery.interpreter.matchers.tuple.ITuple; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.matchers.util.CollectionsFactory.MemoryType; +import tools.refinery.interpreter.matchers.util.IMemoryView; +import tools.refinery.interpreter.matchers.util.IMultiLookup; +import tools.refinery.interpreter.matchers.util.IMultiLookup.ChangeGranularity; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/memories/IdentityMaskedTupleMemory.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/memories/IdentityMaskedTupleMemory.java new file mode 100644 index 00000000..bd693eeb --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.memories; + +import java.util.Collection; +import java.util.Collections; + +import tools.refinery.interpreter.matchers.tuple.ITuple; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/memories/MaskedTupleMemory.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/memories/MaskedTupleMemory.java new file mode 100644 index 00000000..9c179451 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.memories; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; + +import tools.refinery.interpreter.matchers.memories.timely.TimelyDefaultMaskedTupleMemory; +import tools.refinery.interpreter.matchers.memories.timely.TimelyIdentityMaskedTupleMemory; +import tools.refinery.interpreter.matchers.memories.timely.TimelyNullaryMaskedTupleMemory; +import tools.refinery.interpreter.matchers.memories.timely.TimelyUnaryMaskedTupleMemory; +import tools.refinery.interpreter.matchers.tuple.ITuple; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.util.Clearable; +import tools.refinery.interpreter.matchers.util.CollectionsFactory.MemoryType; +import tools.refinery.interpreter.matchers.util.resumable.MaskedResumable; +import tools.refinery.interpreter.matchers.util.timeline.Diff; +import tools.refinery.interpreter.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; + } + +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/memories/NullaryMaskedTupleMemory.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/memories/NullaryMaskedTupleMemory.java new file mode 100644 index 00000000..7ed52358 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.memories; + +import java.util.Collection; +import java.util.Collections; +import java.util.Set; + +import tools.refinery.interpreter.matchers.tuple.ITuple; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.tuple.Tuples; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/memories/UnaryMaskedTupleMemory.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/memories/UnaryMaskedTupleMemory.java new file mode 100644 index 00000000..ccef3a7b --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.memories; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; + +import tools.refinery.interpreter.matchers.tuple.ITuple; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.tuple.Tuples; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.matchers.util.CollectionsFactory.MemoryType; +import tools.refinery.interpreter.matchers.util.IMemoryView; +import tools.refinery.interpreter.matchers.util.IMultiLookup; +import tools.refinery.interpreter.matchers.util.IMultiLookup.ChangeGranularity; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/memories/timely/AbstractTimelyMaskedMemory.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/memories/timely/AbstractTimelyMaskedMemory.java new file mode 100644 index 00000000..9a1312cd --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.matchers.memories.MaskedTupleMemory; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.matchers.util.Signed; +import tools.refinery.interpreter.matchers.util.TimelyMemory; +import tools.refinery.interpreter.matchers.util.timeline.Diff; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/memories/timely/AbstractTimelyTrivialMaskedMemory.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/memories/timely/AbstractTimelyTrivialMaskedMemory.java new file mode 100644 index 00000000..a3d66d0d --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.memories.timely; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + +import tools.refinery.interpreter.matchers.memories.MaskedTupleMemory; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.matchers.util.Signed; +import tools.refinery.interpreter.matchers.util.TimelyMemory; +import tools.refinery.interpreter.matchers.util.timeline.Diff; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/memories/timely/TimelyDefaultMaskedTupleMemory.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/memories/timely/TimelyDefaultMaskedTupleMemory.java new file mode 100644 index 00000000..23cf572b --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.memories.timely; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +import tools.refinery.interpreter.matchers.tuple.ITuple; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.matchers.util.TimelyMemory; +import tools.refinery.interpreter.matchers.util.timeline.Diff; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/memories/timely/TimelyIdentityMaskedTupleMemory.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/memories/timely/TimelyIdentityMaskedTupleMemory.java new file mode 100644 index 00000000..1ab01447 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.matchers.tuple.ITuple; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.matchers.util.timeline.Diff; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/memories/timely/TimelyNullaryMaskedTupleMemory.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/memories/timely/TimelyNullaryMaskedTupleMemory.java new file mode 100644 index 00000000..2c3103db --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.memories.timely; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +import tools.refinery.interpreter.matchers.tuple.ITuple; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.tuple.Tuples; +import tools.refinery.interpreter.matchers.util.timeline.Diff; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/memories/timely/TimelyUnaryMaskedTupleMemory.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/memories/timely/TimelyUnaryMaskedTupleMemory.java new file mode 100644 index 00000000..8a129122 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.matchers.tuple.ITuple; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.matchers.tuple.Tuples; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.matchers.util.TimelyMemory; +import tools.refinery.interpreter.matchers.util.timeline.Diff; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/IOperationCompiler.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/IOperationCompiler.java new file mode 100644 index 00000000..e2301d77 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.planning; + +import java.util.Map; + +import tools.refinery.interpreter.matchers.InterpreterRuntimeException; +import tools.refinery.interpreter.matchers.psystem.IExpressionEvaluator; +import tools.refinery.interpreter.matchers.psystem.queries.PQuery; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.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 InterpreterRuntimeException + */ + public Collector patternCollector(PQuery pattern); + + public void buildConnection(SubPlan parentPlan, Collector collector); + + /** + * @since 0.9 + */ + public void patternFinished(PQuery pattern, Collector collector); + + /** + * @throws InterpreterRuntimeException + */ + 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(); + +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/IQueryPlannerStrategy.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/IQueryPlannerStrategy.java new file mode 100644 index 00000000..c9da01e3 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.planning; + +import org.apache.log4j.Logger; +import tools.refinery.interpreter.matchers.InterpreterRuntimeException; +import tools.refinery.interpreter.matchers.context.IQueryMetaContext; +import tools.refinery.interpreter.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 InterpreterRuntimeException + */ + public SubPlan plan(PBody pSystem, Logger logger, IQueryMetaContext context); +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/QueryProcessingException.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/QueryProcessingException.java new file mode 100644 index 00000000..3efa32a2 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.planning; + +import tools.refinery.interpreter.matchers.InterpreterRuntimeException; + +/** + * @author Zoltan Ujhelyi + * @since 0.9 + */ +public class QueryProcessingException extends InterpreterRuntimeException { + + 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; + } + +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/SubPlan.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/SubPlan.java new file mode 100644 index 00000000..46e6efe3 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.matchers.planning.helpers.TypeHelper; +import tools.refinery.interpreter.matchers.planning.operations.POperation; +import tools.refinery.interpreter.matchers.planning.operations.PProject; +import tools.refinery.interpreter.matchers.planning.operations.PStart; +import tools.refinery.interpreter.matchers.context.IQueryMetaContext; +import tools.refinery.interpreter.matchers.psystem.PBody; +import tools.refinery.interpreter.matchers.psystem.PConstraint; +import tools.refinery.interpreter.matchers.psystem.PVariable; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/SubPlanFactory.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/SubPlanFactory.java new file mode 100644 index 00000000..32014ea6 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.planning; + +import tools.refinery.interpreter.matchers.planning.operations.POperation; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/helpers/BuildHelper.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/helpers/BuildHelper.java new file mode 100644 index 00000000..20ecae2a --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.planning.helpers; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import tools.refinery.interpreter.matchers.planning.operations.PProject; +import tools.refinery.interpreter.matchers.InterpreterRuntimeException; +import tools.refinery.interpreter.matchers.context.IQueryMetaContext; +import tools.refinery.interpreter.matchers.planning.QueryProcessingException; +import tools.refinery.interpreter.matchers.planning.SubPlan; +import tools.refinery.interpreter.matchers.planning.SubPlanFactory; +import tools.refinery.interpreter.matchers.psystem.PBody; +import tools.refinery.interpreter.matchers.psystem.PConstraint; +import tools.refinery.interpreter.matchers.psystem.PVariable; +import tools.refinery.interpreter.matchers.psystem.analysis.QueryAnalyzer; +import tools.refinery.interpreter.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 InterpreterRuntimeException + */ + 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/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/helpers/FunctionalDependencyHelper.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/helpers/FunctionalDependencyHelper.java new file mode 100644 index 00000000..96969fb9 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/helpers/StatisticsHelper.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/helpers/StatisticsHelper.java new file mode 100644 index 00000000..edf67676 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.planning.helpers; + +import java.util.Optional; +import java.util.function.BiFunction; + +import tools.refinery.interpreter.matchers.tuple.TupleMask; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/helpers/TypeHelper.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/helpers/TypeHelper.java new file mode 100644 index 00000000..c9e9a8b2 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.matchers.context.IInputKey; +import tools.refinery.interpreter.matchers.context.IQueryMetaContext; +import tools.refinery.interpreter.matchers.psystem.ITypeInfoProviderConstraint; +import tools.refinery.interpreter.matchers.psystem.PConstraint; +import tools.refinery.interpreter.matchers.psystem.PVariable; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/operations/PApply.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/operations/PApply.java new file mode 100644 index 00000000..dd467598 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.planning.operations; + +import java.util.Collections; +import java.util.Set; + +import tools.refinery.interpreter.matchers.planning.SubPlan; +import tools.refinery.interpreter.matchers.psystem.PConstraint; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/operations/PEnumerate.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/operations/PEnumerate.java new file mode 100644 index 00000000..f9baabd7 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.planning.operations; + +import java.util.Collections; +import java.util.Set; + +import tools.refinery.interpreter.matchers.psystem.EnumerablePConstraint; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/operations/PJoin.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/operations/PJoin.java new file mode 100644 index 00000000..35a4ffac --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.planning.operations; + +import java.util.Collections; +import java.util.Set; + +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/operations/POperation.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/operations/POperation.java new file mode 100644 index 00000000..b1cc52f4 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.planning.operations; + +import java.util.Set; + +import tools.refinery.interpreter.matchers.planning.SubPlan; +import tools.refinery.interpreter.matchers.psystem.PConstraint; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/operations/PProject.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/operations/PProject.java new file mode 100644 index 00000000..adf73344 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.matchers.planning.SubPlan; +import tools.refinery.interpreter.matchers.psystem.PConstraint; +import tools.refinery.interpreter.matchers.psystem.PVariable; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/operations/PStart.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/planning/operations/PStart.java new file mode 100644 index 00000000..d3befbc5 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.matchers.psystem.PConstraint; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/BasePConstraint.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/BasePConstraint.java new file mode 100644 index 00000000..9e18355b --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem; + +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/DeferredPConstraint.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/DeferredPConstraint.java new file mode 100644 index 00000000..90e853f8 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem; + +import java.util.Set; + +import tools.refinery.interpreter.matchers.planning.SubPlan; +import tools.refinery.interpreter.matchers.context.IQueryMetaContext; + +/** + * 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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/EnumerablePConstraint.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/EnumerablePConstraint.java new file mode 100644 index 00000000..a43fa72a --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem; + +import java.util.Set; + +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/IExpressionEvaluator.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/IExpressionEvaluator.java new file mode 100644 index 00000000..197a4dfa --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/IMultiQueryReference.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/IMultiQueryReference.java new file mode 100644 index 00000000..fbce20ed --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem; + +import java.util.Collection; + +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/IQueryReference.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/IQueryReference.java new file mode 100644 index 00000000..cf0ac39b --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem; + +import java.util.Collections; +import java.util.List; + +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/IRelationEvaluator.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/IRelationEvaluator.java new file mode 100644 index 00000000..1db05fd2 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem; + +import java.util.List; +import java.util.Set; + +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/ITypeConstraint.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/ITypeConstraint.java new file mode 100644 index 00000000..cc04d9d1 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; + +import tools.refinery.interpreter.matchers.context.IInputKey; +import tools.refinery.interpreter.matchers.context.IQueryMetaContext; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/ITypeInfoProviderConstraint.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/ITypeInfoProviderConstraint.java new file mode 100644 index 00000000..84b2147f --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem; + +import java.util.Set; + +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/IValueProvider.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/IValueProvider.java new file mode 100644 index 00000000..6eaf952a --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/InitializablePQuery.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/InitializablePQuery.java new file mode 100644 index 00000000..8e087596 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem; + +import java.util.Set; + +import tools.refinery.interpreter.matchers.psystem.annotations.PAnnotation; +import tools.refinery.interpreter.matchers.psystem.queries.PProblem; +import tools.refinery.interpreter.matchers.psystem.queries.PQuery; +import tools.refinery.interpreter.matchers.InterpreterRuntimeException; + +/** + * 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 InterpreterRuntimeException + */ + 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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/KeyedEnumerablePConstraint.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/KeyedEnumerablePConstraint.java new file mode 100644 index 00000000..7337476e --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem; + +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/PBody.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/PBody.java new file mode 100644 index 00000000..483a2aa2 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/PBody.java @@ -0,0 +1,288 @@ +/******************************************************************************* + * 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.interpreter.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.interpreter.matchers.planning.helpers.TypeHelper; +import tools.refinery.interpreter.matchers.psystem.basicdeferred.ExportedParameter; +import tools.refinery.interpreter.matchers.psystem.basicenumerables.ConstantValue; +import tools.refinery.interpreter.matchers.psystem.queries.PDisjunction; +import tools.refinery.interpreter.matchers.psystem.queries.PQuery; +import tools.refinery.interpreter.matchers.context.IQueryMetaContext; +import tools.refinery.interpreter.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 PQuery.PQueryStatus status = PQuery.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(PQuery.PQueryStatus status) { + this.status = status; + } + + public boolean isMutable() { + if (status == null) { + return query.isMutable(); + } else { + return status.equals(PQuery.PQueryStatus.UNINITIALIZED); + } + } + + void checkMutability() { + if (status == null) { + query.checkMutability(); + } else { + Preconditions.checkState(status.equals(PQuery.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/PConstraint.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/PConstraint.java new file mode 100644 index 00000000..4a19f985 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem; + +import java.util.Comparator; +import java.util.Map; +import java.util.Set; + +import tools.refinery.interpreter.matchers.psystem.analysis.QueryAnalyzer; +import tools.refinery.interpreter.matchers.context.IQueryMetaContext; + +/** + * @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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/PTraceable.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/PTraceable.java new file mode 100644 index 00000000..a1bb0bf8 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem; + +/** + * Marker interface for PSystem elements that can be traced. + * @since 1.6 + */ +public interface PTraceable { +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/PVariable.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/PVariable.java new file mode 100644 index 00000000..12ad89a5 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/TypeJudgement.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/TypeJudgement.java new file mode 100644 index 00000000..672e91aa --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.matchers.psystem.basicdeferred.TypeFilterConstraint; +import tools.refinery.interpreter.matchers.psystem.basicenumerables.TypeConstraint; +import tools.refinery.interpreter.matchers.context.IInputKey; +import tools.refinery.interpreter.matchers.context.IQueryMetaContext; +import tools.refinery.interpreter.matchers.context.InputKeyImplication; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/VariableDeferredPConstraint.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/VariableDeferredPConstraint.java new file mode 100644 index 00000000..48610bc6 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem; + +import java.util.Set; + +import tools.refinery.interpreter.matchers.planning.SubPlan; +import tools.refinery.interpreter.matchers.context.IQueryMetaContext; + +/** + * 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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/aggregations/AbstractMemorylessAggregationOperator.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/aggregations/AbstractMemorylessAggregationOperator.java new file mode 100644 index 00000000..54e714e5 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/aggregations/AggregatorType.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/aggregations/AggregatorType.java new file mode 100644 index 00000000..8e6263b0 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/aggregations/BoundAggregator.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/aggregations/BoundAggregator.java new file mode 100644 index 00000000..71363624 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem.aggregations; + +import tools.refinery.interpreter.matchers.context.IInputKey; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/aggregations/IAggregatorFactory.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/aggregations/IAggregatorFactory.java new file mode 100644 index 00000000..9cc309cf --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/aggregations/IMultisetAggregationOperator.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/aggregations/IMultisetAggregationOperator.java new file mode 100644 index 00000000..659d6bd6 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem.aggregations; + +import java.util.Collection; +import java.util.stream.Stream; + +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/analysis/QueryAnalyzer.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/analysis/QueryAnalyzer.java new file mode 100644 index 00000000..967aaa9e --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.matchers.planning.helpers.FunctionalDependencyHelper; +import tools.refinery.interpreter.matchers.psystem.annotations.PAnnotation; +import tools.refinery.interpreter.matchers.psystem.annotations.ParameterReference; +import tools.refinery.interpreter.matchers.psystem.basicdeferred.ExportedParameter; +import tools.refinery.interpreter.matchers.psystem.basicenumerables.PositivePatternCall; +import tools.refinery.interpreter.matchers.context.IQueryBackendContext; +import tools.refinery.interpreter.matchers.context.IQueryMetaContext; +import tools.refinery.interpreter.matchers.psystem.PBody; +import tools.refinery.interpreter.matchers.psystem.PConstraint; +import tools.refinery.interpreter.matchers.psystem.PVariable; +import tools.refinery.interpreter.matchers.psystem.queries.PParameter; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/annotations/PAnnotation.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/annotations/PAnnotation.java new file mode 100644 index 00000000..3a9883ed --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/annotations/ParameterReference.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/annotations/ParameterReference.java new file mode 100644 index 00000000..199b7291 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicdeferred/AggregatorConstraint.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicdeferred/AggregatorConstraint.java new file mode 100644 index 00000000..41870544 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.matchers.context.IInputKey; +import tools.refinery.interpreter.matchers.context.IQueryMetaContext; +import tools.refinery.interpreter.matchers.psystem.ITypeInfoProviderConstraint; +import tools.refinery.interpreter.matchers.psystem.PBody; +import tools.refinery.interpreter.matchers.psystem.PVariable; +import tools.refinery.interpreter.matchers.psystem.TypeJudgement; +import tools.refinery.interpreter.matchers.psystem.aggregations.BoundAggregator; +import tools.refinery.interpreter.matchers.psystem.queries.PQuery; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicdeferred/BaseTypeSafeConstraint.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicdeferred/BaseTypeSafeConstraint.java new file mode 100644 index 00000000..722e5cb4 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem.basicdeferred; + +import java.util.Collections; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import tools.refinery.interpreter.matchers.planning.SubPlan; +import tools.refinery.interpreter.matchers.context.IQueryMetaContext; +import tools.refinery.interpreter.matchers.psystem.PBody; +import tools.refinery.interpreter.matchers.psystem.PVariable; +import tools.refinery.interpreter.matchers.psystem.TypeJudgement; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicdeferred/Equality.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicdeferred/Equality.java new file mode 100644 index 00000000..7649a80a --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.matchers.planning.SubPlan; +import tools.refinery.interpreter.matchers.context.IQueryMetaContext; +import tools.refinery.interpreter.matchers.psystem.DeferredPConstraint; +import tools.refinery.interpreter.matchers.psystem.PBody; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicdeferred/ExportedParameter.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicdeferred/ExportedParameter.java new file mode 100644 index 00000000..246da4ff --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem.basicdeferred; + +import java.util.Collections; +import java.util.Set; + +import tools.refinery.interpreter.matchers.planning.QueryProcessingException; +import tools.refinery.interpreter.matchers.psystem.PBody; +import tools.refinery.interpreter.matchers.psystem.PVariable; +import tools.refinery.interpreter.matchers.psystem.VariableDeferredPConstraint; +import tools.refinery.interpreter.matchers.psystem.queries.PParameter; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicdeferred/ExpressionEvaluation.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicdeferred/ExpressionEvaluation.java new file mode 100644 index 00000000..3f090205 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.matchers.context.IQueryMetaContext; +import tools.refinery.interpreter.matchers.psystem.IExpressionEvaluator; +import tools.refinery.interpreter.matchers.psystem.PBody; +import tools.refinery.interpreter.matchers.psystem.PVariable; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicdeferred/Inequality.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicdeferred/Inequality.java new file mode 100644 index 00000000..f434accf --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem.basicdeferred; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import tools.refinery.interpreter.matchers.psystem.PBody; +import tools.refinery.interpreter.matchers.psystem.PVariable; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicdeferred/NegativePatternCall.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicdeferred/NegativePatternCall.java new file mode 100644 index 00000000..add51f99 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem.basicdeferred; + +import java.util.Collections; +import java.util.Set; + +import tools.refinery.interpreter.matchers.psystem.PBody; +import tools.refinery.interpreter.matchers.psystem.PVariable; +import tools.refinery.interpreter.matchers.psystem.queries.PQuery; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicdeferred/PatternCallBasedDeferred.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicdeferred/PatternCallBasedDeferred.java new file mode 100644 index 00000000..3616a2b1 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem.basicdeferred; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import tools.refinery.interpreter.matchers.planning.QueryProcessingException; +import tools.refinery.interpreter.matchers.psystem.IQueryReference; +import tools.refinery.interpreter.matchers.psystem.PBody; +import tools.refinery.interpreter.matchers.psystem.PConstraint; +import tools.refinery.interpreter.matchers.psystem.PVariable; +import tools.refinery.interpreter.matchers.psystem.VariableDeferredPConstraint; +import tools.refinery.interpreter.matchers.psystem.queries.PQuery; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicdeferred/PatternMatchCounter.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicdeferred/PatternMatchCounter.java new file mode 100644 index 00000000..6a6e7dcf --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem.basicdeferred; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import tools.refinery.interpreter.matchers.context.IQueryMetaContext; +import tools.refinery.interpreter.matchers.psystem.PBody; +import tools.refinery.interpreter.matchers.psystem.PVariable; +import tools.refinery.interpreter.matchers.psystem.queries.PQuery; +import tools.refinery.interpreter.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; + } + +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicdeferred/RelationEvaluation.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicdeferred/RelationEvaluation.java new file mode 100644 index 00000000..45ad19bf --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem.basicdeferred; + +import java.util.List; + +import tools.refinery.interpreter.matchers.psystem.EnumerablePConstraint; +import tools.refinery.interpreter.matchers.psystem.IMultiQueryReference; +import tools.refinery.interpreter.matchers.psystem.IRelationEvaluator; +import tools.refinery.interpreter.matchers.psystem.PBody; +import tools.refinery.interpreter.matchers.psystem.queries.PQuery; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicdeferred/TypeFilterConstraint.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicdeferred/TypeFilterConstraint.java new file mode 100644 index 00000000..46e67757 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem.basicdeferred; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +import tools.refinery.interpreter.matchers.psystem.basicenumerables.TypeConstraint; +import tools.refinery.interpreter.matchers.context.IInputKey; +import tools.refinery.interpreter.matchers.context.IQueryMetaContext; +import tools.refinery.interpreter.matchers.psystem.ITypeConstraint; +import tools.refinery.interpreter.matchers.psystem.PBody; +import tools.refinery.interpreter.matchers.psystem.PVariable; +import tools.refinery.interpreter.matchers.psystem.TypeJudgement; +import tools.refinery.interpreter.matchers.psystem.VariableDeferredPConstraint; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicenumerables/AbstractTransitiveClosure.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicenumerables/AbstractTransitiveClosure.java new file mode 100644 index 00000000..ad60e3cc --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem.basicenumerables; + +import java.util.Set; + +import tools.refinery.interpreter.matchers.context.IQueryMetaContext; +import tools.refinery.interpreter.matchers.psystem.IQueryReference; +import tools.refinery.interpreter.matchers.psystem.ITypeInfoProviderConstraint; +import tools.refinery.interpreter.matchers.psystem.KeyedEnumerablePConstraint; +import tools.refinery.interpreter.matchers.psystem.PBody; +import tools.refinery.interpreter.matchers.psystem.TypeJudgement; +import tools.refinery.interpreter.matchers.psystem.queries.PQuery; +import tools.refinery.interpreter.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); + } + +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicenumerables/BinaryReflexiveTransitiveClosure.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicenumerables/BinaryReflexiveTransitiveClosure.java new file mode 100644 index 00000000..a7c7fae7 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem.basicenumerables; + +import tools.refinery.interpreter.matchers.planning.QueryProcessingException; +import tools.refinery.interpreter.matchers.context.IInputKey; +import tools.refinery.interpreter.matchers.psystem.PBody; +import tools.refinery.interpreter.matchers.psystem.queries.PQuery; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicenumerables/BinaryTransitiveClosure.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicenumerables/BinaryTransitiveClosure.java new file mode 100644 index 00000000..ae961cea --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem.basicenumerables; + +import tools.refinery.interpreter.matchers.psystem.PBody; +import tools.refinery.interpreter.matchers.psystem.queries.PQuery; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicenumerables/Connectivity.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicenumerables/Connectivity.java new file mode 100644 index 00000000..8127926b --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicenumerables/Connectivity.java @@ -0,0 +1,11 @@ +/* + * SPDX-FileCopyrightText: 2023 The Refinery Authors + * + * SPDX-License-Identifier: EPL-2.0 + */ +package tools.refinery.interpreter.matchers.psystem.basicenumerables; + +public enum Connectivity { + WEAK, + STRONG; +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicenumerables/ConstantValue.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicenumerables/ConstantValue.java new file mode 100644 index 00000000..21950a7b --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem.basicenumerables; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import tools.refinery.interpreter.matchers.context.IQueryMetaContext; +import tools.refinery.interpreter.matchers.psystem.KeyedEnumerablePConstraint; +import tools.refinery.interpreter.matchers.psystem.PBody; +import tools.refinery.interpreter.matchers.psystem.PVariable; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicenumerables/PositivePatternCall.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicenumerables/PositivePatternCall.java new file mode 100644 index 00000000..be3556d0 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem.basicenumerables; + +import java.util.HashSet; +import java.util.Set; + +import tools.refinery.interpreter.matchers.context.IInputKey; +import tools.refinery.interpreter.matchers.context.IQueryMetaContext; +import tools.refinery.interpreter.matchers.psystem.IQueryReference; +import tools.refinery.interpreter.matchers.psystem.ITypeInfoProviderConstraint; +import tools.refinery.interpreter.matchers.psystem.KeyedEnumerablePConstraint; +import tools.refinery.interpreter.matchers.psystem.PBody; +import tools.refinery.interpreter.matchers.psystem.TypeJudgement; +import tools.refinery.interpreter.matchers.psystem.queries.PQuery; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.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.interpreter.matchers.psystem.basicenumerables; + +import tools.refinery.interpreter.matchers.context.IQueryMetaContext; +import tools.refinery.interpreter.matchers.psystem.*; +import tools.refinery.interpreter.matchers.psystem.queries.PQuery; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicenumerables/TypeConstraint.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/basicenumerables/TypeConstraint.java new file mode 100644 index 00000000..cc3b8d12 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem.basicenumerables; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +import tools.refinery.interpreter.matchers.context.IInputKey; +import tools.refinery.interpreter.matchers.context.IQueryMetaContext; +import tools.refinery.interpreter.matchers.psystem.ITypeConstraint; +import tools.refinery.interpreter.matchers.psystem.KeyedEnumerablePConstraint; +import tools.refinery.interpreter.matchers.psystem.PBody; +import tools.refinery.interpreter.matchers.psystem.PVariable; +import tools.refinery.interpreter.matchers.psystem.TypeJudgement; +import tools.refinery.interpreter.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); + } +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/queries/BasePQuery.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/queries/BasePQuery.java new file mode 100644 index 00000000..06f01f57 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.matchers.psystem.annotations.PAnnotation; +import tools.refinery.interpreter.matchers.InterpreterRuntimeException; +import tools.refinery.interpreter.matchers.backend.IQueryBackendFactory; +import tools.refinery.interpreter.matchers.backend.QueryEvaluationHint; +import tools.refinery.interpreter.matchers.context.IInputKey; +import tools.refinery.interpreter.matchers.psystem.PBody; +import tools.refinery.interpreter.matchers.psystem.TypeJudgement; +import tools.refinery.interpreter.matchers.tuple.Tuples; +import tools.refinery.interpreter.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 InterpreterRuntimeException + */ + 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; + } + +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/queries/PDisjunction.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/queries/PDisjunction.java new file mode 100644 index 00000000..7f1cfd01 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem.queries; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.stream.Collectors; + +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/queries/PParameter.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/queries/PParameter.java new file mode 100644 index 00000000..8aa26198 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem.queries; + +import java.util.Objects; + +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/queries/PParameterDirection.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/queries/PParameterDirection.java new file mode 100644 index 00000000..d8bdda61 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/queries/PProblem.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/queries/PProblem.java new file mode 100644 index 00000000..03405420 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem.queries; + +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/queries/PQueries.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/queries/PQueries.java new file mode 100644 index 00000000..a4898d09 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/queries/PQueries.java @@ -0,0 +1,109 @@ +/******************************************************************************* + * 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.interpreter.matchers.psystem.queries; + +import tools.refinery.interpreter.matchers.context.IInputKey; +import tools.refinery.interpreter.matchers.psystem.IMultiQueryReference; +import tools.refinery.interpreter.matchers.psystem.ITypeConstraint; +import tools.refinery.interpreter.matchers.psystem.PBody; +import tools.refinery.interpreter.matchers.psystem.PTraceable; +import tools.refinery.interpreter.matchers.psystem.basicenumerables.TypeConstraint; + +import java.util.HashSet; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Stream; + +/** + * 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 PQuery.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/queries/PQuery.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/queries/PQuery.java new file mode 100644 index 00000000..39c08b07 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem.queries; + +import tools.refinery.interpreter.matchers.InterpreterRuntimeException; +import tools.refinery.interpreter.matchers.backend.IQueryBackend; +import tools.refinery.interpreter.matchers.backend.IQueryBackendHintProvider; +import tools.refinery.interpreter.matchers.backend.QueryEvaluationHint; +import tools.refinery.interpreter.matchers.psystem.PBody; +import tools.refinery.interpreter.matchers.psystem.PTraceable; +import tools.refinery.interpreter.matchers.psystem.TypeJudgement; + +import java.util.List; +import java.util.Set; + +/** + * Internal representation of a query / graph pattern (using a constraint system formalism), + * to be interpreted by a query evaluator ({@link IQueryBackend}). + * End-users of Refinery Intepreter 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 InterpreterRuntimeException 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(); + +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/queries/PQueryHeader.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/queries/PQueryHeader.java new file mode 100644 index 00000000..54678755 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem.queries; + +import java.util.List; +import java.util.Optional; + +import tools.refinery.interpreter.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()); + } + +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/queries/PVisibility.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/queries/PVisibility.java new file mode 100644 index 00000000..074648a4 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/queries/QueryInitializationException.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/queries/QueryInitializationException.java new file mode 100644 index 00000000..9d05a0dd --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem.queries; + +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/AbstractRewriterTraceSource.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/AbstractRewriterTraceSource.java new file mode 100644 index 00000000..59422e3e --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem.rewriters; + +import java.util.Objects; + +import tools.refinery.interpreter.matchers.psystem.PConstraint; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/ConstraintRemovalReason.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/ConstraintRemovalReason.java new file mode 100644 index 00000000..4948c1e5 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/DefaultFlattenCallPredicate.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/DefaultFlattenCallPredicate.java new file mode 100644 index 00000000..b2e46ee6 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem.rewriters; +import tools.refinery.interpreter.matchers.psystem.basicenumerables.PositivePatternCall; + +/** + * @author Marton Bur + * + */ +public class DefaultFlattenCallPredicate implements IFlattenCallPredicate { + + @Override + public boolean shouldFlatten(PositivePatternCall positivePatternCall) { + return true; + } + +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/FlattenerCopier.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/FlattenerCopier.java new file mode 100644 index 00000000..cddd182b --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.matchers.psystem.basicdeferred.Equality; +import tools.refinery.interpreter.matchers.psystem.basicdeferred.ExportedParameter; +import tools.refinery.interpreter.matchers.psystem.basicdeferred.ExpressionEvaluation; +import tools.refinery.interpreter.matchers.psystem.basicenumerables.PositivePatternCall; +import tools.refinery.interpreter.matchers.psystem.PBody; +import tools.refinery.interpreter.matchers.psystem.PConstraint; +import tools.refinery.interpreter.matchers.psystem.PVariable; +import tools.refinery.interpreter.matchers.psystem.queries.PQuery; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/IConstraintFilter.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/IConstraintFilter.java new file mode 100644 index 00000000..85c30296 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem.rewriters; + +import tools.refinery.interpreter.matchers.psystem.basicdeferred.ExportedParameter; +import tools.refinery.interpreter.matchers.psystem.PConstraint; + +/** + * 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; + } + + } +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/IDerivativeModificationReason.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/IDerivativeModificationReason.java new file mode 100644 index 00000000..0afc3456 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/IFlattenCallPredicate.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/IFlattenCallPredicate.java new file mode 100644 index 00000000..2d43b17b --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem.rewriters; + +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/IPTraceableTraceProvider.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/IPTraceableTraceProvider.java new file mode 100644 index 00000000..4699c4eb --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem.rewriters; + +import java.util.stream.Stream; + +import tools.refinery.interpreter.matchers.psystem.PTraceable; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/IRewriterTraceCollector.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/IRewriterTraceCollector.java new file mode 100644 index 00000000..883ca737 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem.rewriters; + +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/IVariableRenamer.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/IVariableRenamer.java new file mode 100644 index 00000000..27fc433d --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem.rewriters; + +import tools.refinery.interpreter.matchers.psystem.PVariable; +import tools.refinery.interpreter.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); + } + } +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/MappingTraceCollector.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/MappingTraceCollector.java new file mode 100644 index 00000000..3b9e5d78 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.matchers.psystem.PTraceable; +import tools.refinery.interpreter.matchers.psystem.queries.PQuery; +import tools.refinery.interpreter.matchers.util.CollectionsFactory; +import tools.refinery.interpreter.matchers.util.CollectionsFactory.MemoryType; +import tools.refinery.interpreter.matchers.util.IMemoryView; +import tools.refinery.interpreter.matchers.util.IMultiLookup; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/NeverFlattenCallPredicate.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/NeverFlattenCallPredicate.java new file mode 100644 index 00000000..1d3484bb --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem.rewriters; + +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/NopTraceCollector.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/NopTraceCollector.java new file mode 100644 index 00000000..ce768636 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem.rewriters; + +import java.util.stream.Stream; + +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/PBodyCopier.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/PBodyCopier.java new file mode 100644 index 00000000..99350185 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/PBodyCopier.java @@ -0,0 +1,305 @@ +/******************************************************************************* + * Copyright (c) 2010-2014, Marton Bur, Akos Horvath, 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.interpreter.matchers.psystem.rewriters; + +import tools.refinery.interpreter.matchers.planning.QueryProcessingException; +import tools.refinery.interpreter.matchers.psystem.EnumerablePConstraint; +import tools.refinery.interpreter.matchers.psystem.PBody; +import tools.refinery.interpreter.matchers.psystem.PConstraint; +import tools.refinery.interpreter.matchers.psystem.PVariable; +import tools.refinery.interpreter.matchers.psystem.basicdeferred.*; +import tools.refinery.interpreter.matchers.psystem.basicenumerables.*; +import tools.refinery.interpreter.matchers.psystem.queries.PParameter; +import tools.refinery.interpreter.matchers.psystem.queries.PQuery; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.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 IVariableRenamer.SameName(), new IConstraintFilter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/PBodyNormalizer.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/PBodyNormalizer.java new file mode 100644 index 00000000..5a189394 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.matchers.planning.QueryProcessingException; +import tools.refinery.interpreter.matchers.planning.helpers.TypeHelper; +import tools.refinery.interpreter.matchers.psystem.basicdeferred.Equality; +import tools.refinery.interpreter.matchers.psystem.basicdeferred.Inequality; +import tools.refinery.interpreter.matchers.context.IInputKey; +import tools.refinery.interpreter.matchers.context.IQueryMetaContext; +import tools.refinery.interpreter.matchers.psystem.ITypeConstraint; +import tools.refinery.interpreter.matchers.psystem.ITypeInfoProviderConstraint; +import tools.refinery.interpreter.matchers.psystem.PBody; +import tools.refinery.interpreter.matchers.psystem.PConstraint; +import tools.refinery.interpreter.matchers.psystem.TypeJudgement; +import tools.refinery.interpreter.matchers.psystem.queries.PDisjunction; +import tools.refinery.interpreter.matchers.psystem.queries.PQuery; +import tools.refinery.interpreter.matchers.psystem.queries.PQuery.PQueryStatus; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/PDisjunctionRewriter.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/PDisjunctionRewriter.java new file mode 100644 index 00000000..8eea0585 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem.rewriters; + +import tools.refinery.interpreter.matchers.psystem.queries.PDisjunction; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/PDisjunctionRewriterCacher.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/PDisjunctionRewriterCacher.java new file mode 100644 index 00000000..58491961 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/PQueryFlattener.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/PQueryFlattener.java new file mode 100644 index 00000000..d1447199 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/PQueryFlattener.java @@ -0,0 +1,251 @@ +/******************************************************************************* + * 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.interpreter.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.interpreter.matchers.psystem.basicenumerables.PositivePatternCall; +import tools.refinery.interpreter.matchers.psystem.PBody; +import tools.refinery.interpreter.matchers.psystem.PConstraint; +import tools.refinery.interpreter.matchers.psystem.queries.PDisjunction; +import tools.refinery.interpreter.matchers.psystem.queries.PQuery; +import tools.refinery.interpreter.matchers.psystem.queries.PQuery.PQueryStatus; +import tools.refinery.interpreter.matchers.psystem.rewriters.IConstraintFilter.AllowAllFilter; +import tools.refinery.interpreter.matchers.psystem.rewriters.IConstraintFilter.ExportedParameterFilter; +import tools.refinery.interpreter.matchers.util.Preconditions; +import tools.refinery.interpreter.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; + IVariableRenamer.HierarchicalName hierarchicalNamingTool = new IVariableRenamer.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 IVariableRenamer.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/RewriterException.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/RewriterException.java new file mode 100644 index 00000000..61b5049b --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem.rewriters; + +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/SurrogateQueryRewriter.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/SurrogateQueryRewriter.java new file mode 100644 index 00000000..6ab2a3de --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.psystem.rewriters; + +import java.util.LinkedHashSet; +import java.util.Set; + +import tools.refinery.interpreter.matchers.psystem.basicenumerables.PositivePatternCall; +import tools.refinery.interpreter.matchers.psystem.basicenumerables.TypeConstraint; +import tools.refinery.interpreter.matchers.context.IInputKey; +import tools.refinery.interpreter.matchers.context.surrogate.SurrogateQueryRegistry; +import tools.refinery.interpreter.matchers.psystem.PBody; +import tools.refinery.interpreter.matchers.psystem.PVariable; +import tools.refinery.interpreter.matchers.psystem.queries.PDisjunction; +import tools.refinery.interpreter.matchers.psystem.queries.PQuery; +import tools.refinery.interpreter.matchers.psystem.queries.PQuery.PQueryStatus; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/VariableMappingExpressionEvaluatorWrapper.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/VariableMappingExpressionEvaluatorWrapper.java new file mode 100644 index 00000000..29920a4d --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/psystem/rewriters/VariableMappingExpressionEvaluatorWrapper.java @@ -0,0 +1,90 @@ +/******************************************************************************* + * 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.interpreter.matchers.psystem.rewriters; + +import tools.refinery.interpreter.matchers.psystem.IExpressionEvaluator; +import tools.refinery.interpreter.matchers.psystem.IValueProvider; +import tools.refinery.interpreter.matchers.psystem.PVariable; +import tools.refinery.interpreter.matchers.util.Preconditions; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * 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()) { + // {@code previousMapping} can't be {@code null} if {@code rewrap} is {@code true}. + @SuppressWarnings("squid:S2259") + 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/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/AbstractTuple.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/AbstractTuple.java new file mode 100644 index 00000000..854ce717 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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; + } + +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/BaseFlatTuple.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/BaseFlatTuple.java new file mode 100644 index 00000000..3c13e0fd --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/BaseLeftInheritanceTuple.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/BaseLeftInheritanceTuple.java new file mode 100644 index 00000000..5a6b7564 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/FlatTuple.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/FlatTuple.java new file mode 100644 index 00000000..c28d55db --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/FlatTuple0.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/FlatTuple0.java new file mode 100644 index 00000000..4b5d507a --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/FlatTuple1.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/FlatTuple1.java new file mode 100644 index 00000000..1ab6a379 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/FlatTuple2.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/FlatTuple2.java new file mode 100644 index 00000000..0e128013 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/FlatTuple3.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/FlatTuple3.java new file mode 100644 index 00000000..d44f06f9 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/FlatTuple4.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/FlatTuple4.java new file mode 100644 index 00000000..cd88f3dc --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/IModifiableTuple.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/IModifiableTuple.java new file mode 100644 index 00000000..7719e6bc --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/ITuple.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/ITuple.java new file mode 100644 index 00000000..8394163f --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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(); +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/LeftInheritanceTuple.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/LeftInheritanceTuple.java new file mode 100644 index 00000000..92ca62b2 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/TupleMask0.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/TupleMask0.java new file mode 100644 index 00000000..7e4449fc --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/TupleMaskIdentity.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/TupleMaskIdentity.java new file mode 100644 index 00000000..a3f78560 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/TupleValueProvider.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/TupleValueProvider.java new file mode 100644 index 00000000..c28cc309 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.tuple; + +import java.util.Map; + +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/Tuples.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/Tuples.java new file mode 100644 index 00000000..66c3a71f --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/VolatileMaskedTuple.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/VolatileMaskedTuple.java new file mode 100644 index 00000000..c47a1b89 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.tuple; + +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/VolatileModifiableMaskedTuple.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/VolatileModifiableMaskedTuple.java new file mode 100644 index 00000000..2a388150 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.tuple; + +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/VolatileTuple.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/tuple/VolatileTuple.java new file mode 100644 index 00000000..2e4e0b9d --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/Accuracy.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/Accuracy.java new file mode 100644 index 00000000..8c1dfca4 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/Clearable.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/Clearable.java new file mode 100644 index 00000000..5291cd28 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/CollectionsFactory.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/CollectionsFactory.java new file mode 100644 index 00000000..c1ede988 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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); + } + +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/Direction.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/Direction.java new file mode 100644 index 00000000..18824cbf --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/EclipseCollectionsBagMemory.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/EclipseCollectionsBagMemory.java new file mode 100644 index 00000000..eded382f --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/EclipseCollectionsDeltaBag.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/EclipseCollectionsDeltaBag.java new file mode 100644 index 00000000..139145d8 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/EclipseCollectionsFactory.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/EclipseCollectionsFactory.java new file mode 100644 index 00000000..6907df2f --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/EclipseCollectionsFactory.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.interpreter.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; + +/** + * @author Gabor Bergmann + * @since 1.7 + * @noreference This class is not intended to be referenced by clients. + */ +public class EclipseCollectionsFactory implements CollectionsFactory.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, + CollectionsFactory.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, CollectionsFactory.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/EclipseCollectionsLongMultiset.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/EclipseCollectionsLongMultiset.java new file mode 100644 index 00000000..b05d8cb7 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/EclipseCollectionsLongSetMemory.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/EclipseCollectionsLongSetMemory.java new file mode 100644 index 00000000..c6f1ae96 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/EclipseCollectionsLongSetMemory.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.interpreter.matchers.util; + +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; + +import java.util.Collection; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Set; + +/** + * @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() { + private final LongIterator longIterator = wrapped.longIterator(); + + @Override + public boolean hasNext() { + return longIterator.hasNext(); + } + + @Override + public Long next() { + if (!longIterator.hasNext()) { + throw new NoSuchElementException("next() called, but the iterator is exhausted"); + } + 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/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/EclipseCollectionsMultiLookup.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/EclipseCollectionsMultiLookup.java new file mode 100644 index 00000000..b9836137 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.util; + +import org.eclipse.collections.impl.map.mutable.UnifiedMap; +import org.eclipse.collections.impl.map.mutable.primitive.LongObjectHashMap; +import tools.refinery.interpreter.matchers.util.MarkedMemory.MarkedMultiset; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/EclipseCollectionsMultiset.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/EclipseCollectionsMultiset.java new file mode 100644 index 00000000..c9aed507 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/EclipseCollectionsSetMemory.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/EclipseCollectionsSetMemory.java new file mode 100644 index 00000000..82d255c1 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/EmptyMemory.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/EmptyMemory.java new file mode 100644 index 00000000..9a46b343 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/ICache.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/ICache.java new file mode 100644 index 00000000..3e9c5770 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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); + +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/IDeltaBag.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/IDeltaBag.java new file mode 100644 index 00000000..2651535d --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/IMemory.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/IMemory.java new file mode 100644 index 00000000..037b949d --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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(); + +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/IMemoryView.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/IMemoryView.java new file mode 100644 index 00000000..8510e649 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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; + } +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/IMultiLookup.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/IMultiLookup.java new file mode 100644 index 00000000..59876b7b --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.util; + +import java.util.stream.Stream; + +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/IMultiLookupAbstract.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/IMultiLookupAbstract.java new file mode 100644 index 00000000..d7477a5a --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/IMultiLookupAbstract.java @@ -0,0 +1,483 @@ +/******************************************************************************* + * 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.interpreter.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; + +/** + * 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 MarkedMemory.MarkedSet createMarkedSet(); + + @Override + public default boolean negativesAllowed() { + return false; + } + @Override + default boolean duplicatesAllowed() { + return false; + } + + @Override + public default boolean addToBucket(MarkedMemory.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(MarkedMemory.MarkedSet bucket, Value value, boolean throwIfImpossible) { + return throwIfImpossible ? bucket.removeOne(value) : bucket.removeOneOrNop(value); + } + + @Override + public default Value asSingleton(MarkedMemory.MarkedSet bucket) { + return bucket.size() == 1 ? bucket.iterator().next() : null; + } + + @Override + public default MarkedMemory.MarkedSet createSingletonBucket(Value value) { + MarkedMemory.MarkedSet result = createMarkedSet(); + result.addOne(value); + return result; + } + + @Override + public default MarkedMemory.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 + +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/IMultiset.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/IMultiset.java new file mode 100644 index 00000000..dfbb9d92 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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); + +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/IProvider.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/IProvider.java new file mode 100644 index 00000000..5b26438c --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.util; + +import java.util.function.Function; +import java.util.function.Supplier; + +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/ISetMemory.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/ISetMemory.java new file mode 100644 index 00000000..feb0dd5e --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/MapBackedMemoryView.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/MapBackedMemoryView.java new file mode 100644 index 00000000..d11e6807 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/MarkedMemory.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/MarkedMemory.java new file mode 100644 index 00000000..5c76c28b --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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 {} +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/MemoryViewBackedMapView.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/MemoryViewBackedMapView.java new file mode 100644 index 00000000..ba36d630 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/Preconditions.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/Preconditions.java new file mode 100644 index 00000000..484ec07b --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/PurgableCache.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/PurgableCache.java new file mode 100644 index 00000000..af969ba4 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/Sets.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/Sets.java new file mode 100644 index 00000000..6647b657 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/Signed.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/Signed.java new file mode 100644 index 00000000..47808dfd --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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(); + } + +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/SingletonInstanceProvider.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/SingletonInstanceProvider.java new file mode 100644 index 00000000..6074adce --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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; + } + +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/SingletonMemoryView.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/SingletonMemoryView.java new file mode 100644 index 00000000..d368f629 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/TimelyMemory.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/TimelyMemory.java new file mode 100644 index 00000000..4aebfa7a --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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.interpreter.matchers.tuple.ITuple; +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.matchers.util.resumable.Resumable; +import tools.refinery.interpreter.matchers.util.resumable.UnmaskedResumable; +import tools.refinery.interpreter.matchers.util.timeline.Diff; +import tools.refinery.interpreter.matchers.util.timeline.Timeline; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/resumable/MaskedResumable.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/resumable/MaskedResumable.java new file mode 100644 index 00000000..f7d48536 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.util.resumable; + +import java.util.Map; + +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/resumable/Resumable.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/resumable/Resumable.java new file mode 100644 index 00000000..f3d70834 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/resumable/UnmaskedResumable.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/resumable/UnmaskedResumable.java new file mode 100644 index 00000000..ff97fe77 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.util.resumable; + +import java.util.Map; + +import tools.refinery.interpreter.matchers.tuple.Tuple; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/timeline/CompactTimeline.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/timeline/CompactTimeline.java new file mode 100644 index 00000000..6406c387 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.util.timeline; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/timeline/Diff.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/timeline/Diff.java new file mode 100644 index 00000000..e70629f9 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.util.timeline; + +import java.util.ArrayList; + +import tools.refinery.interpreter.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); + } + } + } + +} diff --git a/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/timeline/SingletonTimeline.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/timeline/SingletonTimeline.java new file mode 100644 index 00000000..4a08d22e --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.util.timeline; + +import java.util.Collections; + +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/timeline/Timeline.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/timeline/Timeline.java new file mode 100644 index 00000000..264a7dc8 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.matchers.util.timeline; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import tools.refinery.interpreter.matchers.util.Direction; +import tools.refinery.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/timeline/Timelines.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/matchers/util/timeline/Timelines.java new file mode 100644 index 00000000..ea37f4a9 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/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.interpreter.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/interpreter/src/main/java/tools/refinery/interpreter/util/InterpreterLoggingUtil.java b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/util/InterpreterLoggingUtil.java new file mode 100644 index 00000000..5bcfa892 --- /dev/null +++ b/subprojects/interpreter/src/main/java/tools/refinery/interpreter/util/InterpreterLoggingUtil.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * 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.interpreter.util; + +import org.apache.log4j.Logger; + +/** + * Centralized logger of the Refinery Interpreter runtime. + * @author Bergmann Gabor + * + */ +public class InterpreterLoggingUtil { + + private InterpreterLoggingUtil() {/*Utility class constructor*/} + + /* + * + * Provides a static default logger. + */ + public static Logger getDefaultLogger() { + if (defaultRuntimeLogger == null) { + defaultRuntimeLogger = Logger.getLogger("tools.refinery.interpreter"); + if (defaultRuntimeLogger == null) + throw new AssertionError( + "Configuration error: unable to create default Refinery Interpreter 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 Refinery Interpreter 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 Refinery Interpreter 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-54-g00ecf