From ca2562de7dffd400d8a70a69c44db574ca237d08 Mon Sep 17 00:00:00 2001 From: Zoltan Ujhelyi Date: Fri, 1 Mar 2019 13:37:30 +0100 Subject: [PATCH] Intoduces a MagicDrawProjectScope #25 This change provides a new scope and engine context implementation that refreshes the base index when new projects are attached to the handled MagicDraw project. --- .../com/incquerylabs/v4md/V4MDPlugin.java | 54 +++-- .../incquerylabs/v4md/ViatraQueryAdapter.java | 42 ++-- .../MagicDrawProjectEngineContext.java | 190 ++++++++++++++++++ .../v4md/internal/MagicDrawProjectScope.java | 90 +++++++++ 4 files changed, 335 insertions(+), 41 deletions(-) create mode 100644 com.incquerylabs.v4md/src/main/com/incquerylabs/v4md/internal/MagicDrawProjectEngineContext.java create mode 100644 com.incquerylabs.v4md/src/main/com/incquerylabs/v4md/internal/MagicDrawProjectScope.java diff --git a/com.incquerylabs.v4md/src/main/com/incquerylabs/v4md/V4MDPlugin.java b/com.incquerylabs.v4md/src/main/com/incquerylabs/v4md/V4MDPlugin.java index 8e067d6..cd388b9 100644 --- a/com.incquerylabs.v4md/src/main/com/incquerylabs/v4md/V4MDPlugin.java +++ b/com.incquerylabs.v4md/src/main/com/incquerylabs/v4md/V4MDPlugin.java @@ -1,28 +1,56 @@ package com.incquerylabs.v4md; import com.incquerylabs.v4md.expressions.BinaryVQLExpression; +import com.nomagic.ci.persistence.IProject; import com.nomagic.magicdraw.core.Application; import com.nomagic.magicdraw.core.Project; +import com.nomagic.magicdraw.core.ProjectUtilities; +import com.nomagic.magicdraw.core.modules.ModuleUsage; import com.nomagic.magicdraw.core.project.ProjectEventListenerAdapter; +import com.nomagic.magicdraw.core.project.ProjectPartListener; import com.nomagic.magicdraw.expressions.evaluation.ExpressionEvaluationConfigurator; public class V4MDPlugin extends com.nomagic.magicdraw.plugins.Plugin { + private final class V4MDProjectListener extends ProjectEventListenerAdapter implements ProjectPartListener { + @Override + public void projectOpened(Project project) { + ViatraQueryAdapter.getOrCreateAdapter(project); + } + + @Override + public void projectPreClosed(Project project) { + ViatraQueryAdapter.disposeAdapter(project); + } + + @Override + public void projectPartLoaded(Project project, IProject storage) { + ViatraQueryAdapter.getAdapter(project).ifPresent(ViatraQueryAdapter::projectStructureUpdated); + } + + @Override + public void projectPartAttached(ModuleUsage usage) { + Project project = ProjectUtilities.getProject(usage.getUsed()); + ViatraQueryAdapter.getAdapter(project).ifPresent(ViatraQueryAdapter::projectStructureUpdated); + } + + @Override + public void projectPartDetached(ModuleUsage usage) { + Project project = ProjectUtilities.getProject(usage.getUsed()); + ViatraQueryAdapter.getAdapter(project).ifPresent(ViatraQueryAdapter::projectStructureUpdated); + + } + + @Override + public void projectPartRemoved(IProject project) { + Project modelProject = ProjectUtilities.getProject(project); + ViatraQueryAdapter.getAdapter(modelProject).ifPresent(ViatraQueryAdapter::projectStructureUpdated); + } + } + @Override public void init() { - Application.getInstance().getProjectsManager().addProjectListener(new ProjectEventListenerAdapter() { - - @Override - public void projectOpened(Project project) { - ViatraQueryAdapter.getOrCreateAdapter(project); - } - - @Override - public void projectPreClosed(Project project) { - ViatraQueryAdapter.disposeAdapter(project); - } - - }); + Application.getInstance().getProjectsManager().addProjectListener(new V4MDProjectListener()); // Registers an expression evaluator for generated VIATRA queries ExpressionEvaluationConfigurator.getInstance().registerFactory(BinaryVQLExpression.LANGUAGE, diff --git a/com.incquerylabs.v4md/src/main/com/incquerylabs/v4md/ViatraQueryAdapter.java b/com.incquerylabs.v4md/src/main/com/incquerylabs/v4md/ViatraQueryAdapter.java index 729f59b..8473d28 100644 --- a/com.incquerylabs.v4md/src/main/com/incquerylabs/v4md/ViatraQueryAdapter.java +++ b/com.incquerylabs.v4md/src/main/com/incquerylabs/v4md/ViatraQueryAdapter.java @@ -1,29 +1,21 @@ package com.incquerylabs.v4md; -import java.util.Arrays; import java.util.Objects; import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; import org.eclipse.emf.common.notify.Notifier; import org.eclipse.emf.common.notify.impl.AdapterImpl; -import org.eclipse.emf.ecore.EReference; import org.eclipse.viatra.query.runtime.api.AdvancedViatraQueryEngine; -import org.eclipse.viatra.query.runtime.base.api.BaseIndexOptions; -import org.eclipse.viatra.query.runtime.emf.EMFScope; import org.eclipse.viatra.query.runtime.exception.ViatraQueryException; +import com.incquerylabs.v4md.internal.MagicDrawProjectScope; import com.nomagic.magicdraw.core.Project; public class ViatraQueryAdapter extends AdapterImpl{ private AdvancedViatraQueryEngine engine; private Project project; - - private ViatraQueryAdapter(AdvancedViatraQueryEngine engine, Project project) { this.engine = engine; @@ -43,6 +35,13 @@ public void wipeEngine(){ engine.wipe(); } + /** + * This method is called by a project change listener registered by V4MD; it should not be called by end-users. + */ + void projectStructureUpdated() { + ((MagicDrawProjectScope)engine.getScope()).projectStructureUpdated(); + } + public static Optional getAdapter(Project project) { Objects.requireNonNull(project, "ViatraQueryAdapter cannot be provided for a null Project"); return Optional.ofNullable(project.getPrimaryModel()).flatMap(m -> m.eAdapters().stream() @@ -51,15 +50,10 @@ public static Optional getAdapter(Project project) { public static ViatraQueryAdapter getOrCreateAdapter(Project project, Notifier... notifiers) { return getAdapter(project).orElseGet(() -> { - ViatraQueryAdapter adapter = - new ViatraQueryAdapter( - createQueryEngine( - Stream.concat(project.getModels().stream(), Arrays.stream(notifiers)) - .collect(Collectors.toSet())), - project); - project.getPrimaryModel().eAdapters().add(adapter); - return adapter; - }); + ViatraQueryAdapter adapter = new ViatraQueryAdapter(createQueryEngine(project, notifiers), project); + project.getPrimaryModel().eAdapters().add(adapter); + return adapter; + }); } public static ViatraQueryAdapter getOrCreateAdapter(Project project) { @@ -69,16 +63,8 @@ public static ViatraQueryAdapter getOrCreateAdapter(Project project) { public static void disposeAdapter(Project project) { getAdapter(project).ifPresent(ViatraQueryAdapter::dispose); } - - private static AdvancedViatraQueryEngine createQueryEngine(Set setOfRootElement) throws ViatraQueryException { - // XXX Omitting references can cause semantic errors (so far we are in the clear though) - // these references are only present in UML profiles, typically their contents are equal to the original references inherited from the UML type hierarchy, however there are some cases when this might not be the case. - BaseIndexOptions options = new BaseIndexOptions() - .withFeatureFilterConfiguration(reference -> reference instanceof EReference - && ((EReference) reference).isContainment() && reference.getName().contains("_from_")) - .withStrictNotificationMode(false); - - return AdvancedViatraQueryEngine.createUnmanagedEngine(new EMFScope(setOfRootElement, options)); + private static AdvancedViatraQueryEngine createQueryEngine(Project project, Notifier... notifiers) throws ViatraQueryException { + return AdvancedViatraQueryEngine.createUnmanagedEngine(new MagicDrawProjectScope(project, notifiers)); } } diff --git a/com.incquerylabs.v4md/src/main/com/incquerylabs/v4md/internal/MagicDrawProjectEngineContext.java b/com.incquerylabs.v4md/src/main/com/incquerylabs/v4md/internal/MagicDrawProjectEngineContext.java new file mode 100644 index 0000000..5d20880 --- /dev/null +++ b/com.incquerylabs.v4md/src/main/com/incquerylabs/v4md/internal/MagicDrawProjectEngineContext.java @@ -0,0 +1,190 @@ +package com.incquerylabs.v4md.internal; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.log4j.Logger; +import org.eclipse.emf.common.notify.Notifier; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.viatra.query.runtime.api.ViatraQueryEngine; +import org.eclipse.viatra.query.runtime.api.scope.IBaseIndex; +import org.eclipse.viatra.query.runtime.api.scope.IEngineContext; +import org.eclipse.viatra.query.runtime.api.scope.IIndexingErrorListener; +import org.eclipse.viatra.query.runtime.base.api.BaseIndexOptions; +import org.eclipse.viatra.query.runtime.base.api.NavigationHelper; +import org.eclipse.viatra.query.runtime.base.api.filters.IBaseIndexObjectFilter; +import org.eclipse.viatra.query.runtime.base.api.filters.IBaseIndexResourceFilter; +import org.eclipse.viatra.query.runtime.base.core.NavigationHelperImpl; +import org.eclipse.viatra.query.runtime.base.exception.ViatraBaseException; +import org.eclipse.viatra.query.runtime.emf.DynamicEMFQueryRuntimeContext; +import org.eclipse.viatra.query.runtime.emf.EMFBaseIndexWrapper; +import org.eclipse.viatra.query.runtime.emf.EMFQueryRuntimeContext; +import org.eclipse.viatra.query.runtime.matchers.ViatraQueryRuntimeException; +import org.eclipse.viatra.query.runtime.matchers.context.IQueryRuntimeContext; + +import com.google.common.collect.Sets; +import com.incquerylabs.v4md.internal.MagicDrawProjectScope.IProjectChangedListener; + +/** + * Provides a specific engine context implementation for MagicDraw projects. + * + * The implementation is based on EMFEngineContext from VIATRA 2.1.1 + */ +class MagicDrawProjectEngineContext implements IEngineContext { + + private final MagicDrawProjectScope scope; + ViatraQueryEngine engine; + Logger logger; + MagicDrawProjectNavigationHelper navHelper; + IBaseIndex baseIndex; + IIndexingErrorListener taintListener; + private EMFQueryRuntimeContext runtimeContext; + + private IProjectChangedListener scopeListener = this::modelSetUpdated; + + private void modelSetUpdated() { + Set customNotifiers = scope.getCustomNotifiers().collect(Collectors.toSet()); + Set projectRoots = scope.getProjectModels().collect(Collectors.toSet()); + MagicDrawProjectNavigationHelper navigationHelper = getNavHelper(true); + Set actualModelRoots = navigationHelper.getModelRoots(); + + Set rootsToAdd = Sets.difference(projectRoots, actualModelRoots); + Set rootsToRemove = Sets.difference(Sets.difference(actualModelRoots, projectRoots), customNotifiers); + + try { + navigationHelper.coalesceTraversals(() -> { + while (!rootsToRemove.isEmpty()) { + navigationHelper.removeRoot(rootsToRemove.iterator().next()); + } + while (!rootsToAdd.isEmpty()) { + navigationHelper.addRoot(rootsToAdd.iterator().next()); + } + }); + } catch (InvocationTargetException e) { + logger.fatal("Error while updating project", e); + } + } + + /** + * Customizes the NavigationHelperImpl by adding a new removeRoot method + * + */ + private class MagicDrawProjectNavigationHelper extends NavigationHelperImpl { + + public MagicDrawProjectNavigationHelper(Notifier emfRoot, BaseIndexOptions options, Logger logger) { + super(emfRoot, options, logger); + } + + Set getModelRoots() { + return modelRoots; + } + + public 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.add(root); + // TODO contentAdapter.removeAdapter(root); removeAdapter is not visible here + Method method; + try { + method = contentAdapter.getClass().getMethod("removeAdaper", Notifier.class); + method.setAccessible(true); + method.invoke(contentAdapter, root); + } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + logger.fatal("Error while updating project", e); + } + notifyBaseIndexChangeListeners(); + } + } + + public MagicDrawProjectEngineContext(MagicDrawProjectScope scope, ViatraQueryEngine engine, IIndexingErrorListener taintListener, Logger logger) { + this.scope = scope; + this.engine = engine; + this.logger = logger; + this.taintListener = taintListener; + scope.addProjectChangeListener(scopeListener); + } + + /** + * @throws ViatraQueryRuntimeException thrown if the navigation helper cannot be initialized + */ + public NavigationHelper getNavHelper() { + return getNavHelper(true); + } + + private MagicDrawProjectNavigationHelper getNavHelper(boolean ensureInitialized) { + if (navHelper == null) { + navHelper = new MagicDrawProjectNavigationHelper(null, this.scope.getOptions(), logger); + + + getBaseIndex().addIndexingErrorListener(taintListener); + if (ensureInitialized) { + ensureIndexLoaded(); + } + } + return navHelper; + } + + private void ensureIndexLoaded() { + for (Notifier scopeRoot : this.scope.getScopeRoots()) { + navHelper.addRoot(scopeRoot); + } + } + + @Override + public IQueryRuntimeContext getQueryRuntimeContext() { + NavigationHelper nh = getNavHelper(false); + if (runtimeContext == null) { + runtimeContext = + scope.getOptions().isDynamicEMFMode() ? + new DynamicEMFQueryRuntimeContext(nh, logger, scope) : + new EMFQueryRuntimeContext(nh, logger, scope); + + ensureIndexLoaded(); + } + + return runtimeContext; + } + + @Override + public void dispose() { + if (runtimeContext != null) runtimeContext.dispose(); + if (navHelper != null) navHelper.dispose(); + + scope.removeProjectChangeListener(scopeListener); + + this.baseIndex = null; + this.engine = null; + this.logger = null; + this.navHelper = null; + } + + + @Override + public IBaseIndex getBaseIndex() { + if (baseIndex == null) { + final NavigationHelper navigationHelper = getNavHelper(); + baseIndex = new EMFBaseIndexWrapper(navigationHelper); + } + return baseIndex; + } + +} \ No newline at end of file diff --git a/com.incquerylabs.v4md/src/main/com/incquerylabs/v4md/internal/MagicDrawProjectScope.java b/com.incquerylabs.v4md/src/main/com/incquerylabs/v4md/internal/MagicDrawProjectScope.java new file mode 100644 index 0000000..153809d --- /dev/null +++ b/com.incquerylabs.v4md/src/main/com/incquerylabs/v4md/internal/MagicDrawProjectScope.java @@ -0,0 +1,90 @@ +package com.incquerylabs.v4md.internal; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.log4j.Logger; +import org.eclipse.emf.common.notify.Notifier; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.eclipse.viatra.query.runtime.api.ViatraQueryEngine; +import org.eclipse.viatra.query.runtime.api.scope.IEngineContext; +import org.eclipse.viatra.query.runtime.api.scope.IIndexingErrorListener; +import org.eclipse.viatra.query.runtime.base.api.BaseIndexOptions; +import org.eclipse.viatra.query.runtime.emf.EMFScope; + +import com.nomagic.magicdraw.core.Project; + +public class MagicDrawProjectScope extends EMFScope { + + private final Project project; + private Notifier[] customNotifiers; + private List listeners = new ArrayList<>(); + + // XXX Omitting references can cause semantic errors (so far we are in the clear though) + // these references are only present in UML profiles, typically their contents are equal to the original references inherited from the UML type hierarchy, however there are some cases when this might not be the case. + private static final BaseIndexOptions BASE_OPTIONS = new BaseIndexOptions() + .withFeatureFilterConfiguration(reference -> reference instanceof EReference + && ((EReference) reference).isContainment() && reference.getName().contains("_from_")) + .withStrictNotificationMode(false); + + public interface IProjectChangedListener { + void modelSetUpdated(); + } + static Stream getProjectModels(Project projectModel) { + return projectModel.getModels().stream(); + } + + static Stream getCustomNotifiers(Notifier... notifiers) { + return Arrays.stream(notifiers); + } + + public MagicDrawProjectScope(Project project, Notifier... notifiers) { + super(Stream.concat(getProjectModels(project), getCustomNotifiers(notifiers)).collect(Collectors.toSet()), + BASE_OPTIONS); + this.project = project; + this.customNotifiers = notifiers; + } + + public Project getProject() { + return project; + } + + @Override + protected IEngineContext createEngineContext(ViatraQueryEngine engine, IIndexingErrorListener errorListener, + Logger logger) { + return new MagicDrawProjectEngineContext(this, engine, errorListener, logger); + } + + boolean addProjectChangeListener(IProjectChangedListener listener) { + return listeners.add(listener); + } + + boolean removeProjectChangeListener(IProjectChangedListener listener) { + return listeners.remove(listener); + } + + public void projectStructureUpdated() { + for (IProjectChangedListener listener : listeners) { + listener.modelSetUpdated(); + } + } + + Stream getProjectModels() { + return getProjectModels(project); + } + + Stream getCustomNotifiers() { + return getCustomNotifiers(customNotifiers); + } + + public boolean isIndexed(EObject element) { + Set modelRoots = Stream.concat(getProjectModels(), getCustomNotifiers()).collect(Collectors.toSet()); + return EcoreUtil.isAncestor(modelRoots, element); + } +}