From 9adbb3d49899a87b3026c11cb4ba3ff77f4fb75b Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Sat, 19 Aug 2023 02:31:57 +0200 Subject: chore: import VIATRA source Make our modifications more maintainable by editing the source code directly instead of using reflection. --- subprojects/viatra-runtime-base/about.html | 26 + subprojects/viatra-runtime-base/build.gradle.kts | 19 + .../viatra/runtime/base/ViatraBasePlugin.java | 46 + .../viatra/runtime/base/api/BaseIndexOptions.java | 395 +++++ .../viatra/runtime/base/api/DataTypeListener.java | 44 + .../base/api/EMFBaseIndexChangeListener.java | 33 + .../viatra/runtime/base/api/FeatureListener.java | 48 + .../runtime/base/api/IEClassifierProcessor.java | 25 + .../base/api/IEMFIndexingErrorListener.java | 22 + .../runtime/base/api/IQueryResultSetter.java | 63 + .../base/api/IQueryResultUpdateListener.java | 45 + .../api/IStructuralFeatureInstanceProcessor.java | 19 + .../viatra/runtime/base/api/IndexingLevel.java | 113 ++ .../viatra/runtime/base/api/InstanceListener.java | 41 + .../base/api/LightweightEObjectObserver.java | 32 + .../api/LightweightEObjectObserverAdapter.java | 74 + .../viatra/runtime/base/api/NavigationHelper.java | 885 ++++++++++ .../base/api/QueryResultAssociativeStore.java | 322 ++++ .../viatra/runtime/base/api/QueryResultMap.java | 210 +++ .../runtime/base/api/TransitiveClosureHelper.java | 26 + .../viatra/runtime/base/api/ViatraBaseFactory.java | 180 +++ .../base/api/filters/IBaseIndexFeatureFilter.java | 38 + .../base/api/filters/IBaseIndexObjectFilter.java | 30 + .../base/api/filters/IBaseIndexResourceFilter.java | 27 + .../base/api/filters/SimpleBaseIndexFilter.java | 46 + .../base/api/profiler/BaseIndexProfiler.java | 79 + .../runtime/base/api/profiler/ProfilerMode.java | 22 + .../base/comprehension/EMFModelComprehension.java | 356 ++++ .../runtime/base/comprehension/EMFVisitor.java | 145 ++ .../WellbehavingDerivedFeatureRegistry.java | 154 ++ .../runtime/base/core/AbstractBaseIndexStore.java | 28 + .../base/core/EMFBaseIndexInstanceStore.java | 451 ++++++ .../runtime/base/core/EMFBaseIndexMetaStore.java | 380 +++++ .../base/core/EMFBaseIndexStatisticsStore.java | 71 + .../viatra/runtime/base/core/EMFDataSource.java | 137 ++ .../base/core/NavigationHelperContentAdapter.java | 750 +++++++++ .../runtime/base/core/NavigationHelperImpl.java | 1702 ++++++++++++++++++++ .../runtime/base/core/NavigationHelperSetting.java | 73 + .../runtime/base/core/NavigationHelperType.java | 14 + .../runtime/base/core/NavigationHelperVisitor.java | 441 +++++ .../base/core/TransitiveClosureHelperImpl.java | 153 ++ .../ProfilingNavigationHelperContentAdapter.java | 155 ++ .../base/exception/ViatraBaseException.java | 25 + 43 files changed, 7945 insertions(+) create mode 100644 subprojects/viatra-runtime-base/about.html create mode 100644 subprojects/viatra-runtime-base/build.gradle.kts create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/ViatraBasePlugin.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/BaseIndexOptions.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/DataTypeListener.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/EMFBaseIndexChangeListener.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/FeatureListener.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IEClassifierProcessor.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IEMFIndexingErrorListener.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IQueryResultSetter.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IQueryResultUpdateListener.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IStructuralFeatureInstanceProcessor.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/IndexingLevel.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/InstanceListener.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/LightweightEObjectObserver.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/LightweightEObjectObserverAdapter.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/NavigationHelper.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/QueryResultAssociativeStore.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/QueryResultMap.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/TransitiveClosureHelper.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/ViatraBaseFactory.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/filters/IBaseIndexFeatureFilter.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/filters/IBaseIndexObjectFilter.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/filters/IBaseIndexResourceFilter.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/filters/SimpleBaseIndexFilter.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/profiler/BaseIndexProfiler.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/api/profiler/ProfilerMode.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/comprehension/EMFModelComprehension.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/comprehension/EMFVisitor.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/comprehension/WellbehavingDerivedFeatureRegistry.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/AbstractBaseIndexStore.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/EMFBaseIndexInstanceStore.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/EMFBaseIndexMetaStore.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/EMFBaseIndexStatisticsStore.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/EMFDataSource.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperContentAdapter.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperImpl.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperSetting.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperType.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/NavigationHelperVisitor.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/TransitiveClosureHelperImpl.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/core/profiler/ProfilingNavigationHelperContentAdapter.java create mode 100644 subprojects/viatra-runtime-base/src/main/java/tools/refinery/viatra/runtime/base/exception/ViatraBaseException.java (limited to 'subprojects/viatra-runtime-base') diff --git a/subprojects/viatra-runtime-base/about.html b/subprojects/viatra-runtime-base/about.html new file mode 100644 index 00000000..d1d5593a --- /dev/null +++ b/subprojects/viatra-runtime-base/about.html @@ -0,0 +1,26 @@ + + + + +About + + + +

About This Content

+ +

March 18, 2019

+

License

+ +

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

+ +

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

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

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

+ * + *

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

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

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

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

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

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

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

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

+ * + *

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

+ * + *

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

+ * + *

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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